- В Java имеются: условный оператор if, оператор выбора case, условное выражение …?... : … , операторы инкремента ++ и декремента -- ,
- В Java имеются операторы цикла: for , while – цикл с предусловием, do...while – цикл с постусловием. А также операторы прерывания циклов continue и break, подпрограмм -return, программы - System.exit.
- Сравнение на равенство чисел в формате с плавающей точкой практически всегда некорректно. Но даже сравнение на неравенство таких чисел нельзя выполнять в случае, когда в точной арифметике эти числа должны быть равны. По этой причине нельзя использовать циклы с вещественным счётчиком в случаях, когда на интервале изменения счётчика укладывается целое число шагов счётчика.
Глава 6. Начальные сведения об объектном программировании
В части 2 мы уже познакомились с первым принципом объектного программирования – инкапсуляцией. Затем научились пользоваться уже готовыми классами. Это – начальная стадия изучения объектного программирования. Для того чтобы овладеть его основными возможностями, требуется научиться создавать собственные классы, изменяющие и усложняющие поведение существующих классов. Важнейшими элементами такого умения является использование наследования и полиморфизма.
Наследование и полиморфизм. UML-диаграммы
Наследование опирается на инкапсуляцию. Оно позволяет строить на основе первоначального класса новые, добавляя в классы новые поля данных и методы. Первоначальный класс называется прародителем (ancestor), новые классы – его потомками (descendants). От потомков, в свою очередь, можно наследовать, получая очередных потомков. И так далее. Набор классов, связанных отношением наследования, называется иерархией классов. А класс, стоящий во главе иерархии, от которого унаследованы все остальные (прямо или опосредованно), называется базовым классом иерархии. В Java все классы являются потомками класса Object. То есть он является базовым для всех классов. Тем не менее, если рассматривается поведение, характерное для объектов какого-то класса и всех потомков этого класса, говорят об иерархии, начинающейся с этого класса, В этом случае именно он является базовым классом иерархии.
Полиморфизм опирается как на инкапсуляцию, так и на наследование. Как показывает опыт преподавания, это наиболее сложный для понимания принцип. Слово “полиморфизм” в переводе с греческого означает “имеющий много форм”. В объектном программировании под полиморфизмом подразумевается наличие кода, написанного для объектов, имеющих тип базового класса иерархии. При этом такой код должен правильно работать для любого объекта, являющегося экземпляром класса из данной иерархии. Независимо от того, где этот класс расположен в иерархии. Такой код и называется полиморфным. При написании полиморфного кода заранее неизвестно, для объектов какого типа он будет работать – один и тот же метод будет исполняться по-разному в зависимости от типа объекта. Пусть, например, у нас имеется класс Figure-“фигура”, и в нём заданы методы show()– показать фигуру на экране, и и hide() - скрыть её. Тогда для переменной figure типа Figure вызовы figure.show() и figure.hide() будут показывать или скрывать объект, на который ссылается эта переменная. Причём сам объект “знает”, как себя показывать или скрывать, а код пишется на уровне абстракций этих действий.
Основное преимущество объектного программирования по сравнению с процедурным как раз и заключается в возможности написания полиморфного кода. Именно для этого пишется иерархия классов. Полиморфизм позволяет резко увеличить коэффициент повторного использования программного кода и его модифицируемость по сравнению с процедурным программированием.
В качестве примера того, как строится иерархия, рассмотрим иерархию фигур, отрисовываемых на экране – она показана на рисунке. В ней базовым классом является Figure, от которого наследуются Dot – “точка”, Triangle – “треугольник” и Square – “квадрат”. От Dot наследуется класс Circle – “окружность”, а от Circle унаследуем Ellipse – “эллипс”. И, наконец, от Square унаследуем Rectangle – “прямоугольник”.
Отметим, что в иерархии принято рисовать стрелки в направлении от наследника к прародителю. Такое направление называется Generalization – “обобщение”, “генерализация”. Оно противоположно направлению наследования, которое принято называть Specialization – “специализация”. Стрелки символизируют направление в сторону упрощения.
Иерархия фигур, отрисовываемых на экране
Часто класс-прародитель называют суперклассом (superclass), а класс-наследник – субклассом (subclass). Но такая терминология подталкивает начинающих программистов к неверной логике: суперкласс пытаются сделать “суперсложным”. Так, чтобы его подклассы (это неверно воспринимается синонимом выражению “упрощённые разновидности”) обладали упрощённым по сравнению с ним поведением. На деле же потомки должны обладать более сложным устройством и поведением по сравнению прародителем. Поэтому в данном учебном пособии предпочтение отдаётся терминам “прародитель” и “наследник”.
Чем ближе к основанию иерархии лежит класс, тем более общим и универсальным (general) он является. И одновременно – более простым. Класс, который лежит в основе иерархии, называется базовым классом этой иерархии. Базовый класс всегда называют именем, которое характеризует все объекты - экземпляры классов-наследников, и которое выражает наиболее общую абстракцию, применимую к таким объектам. В нашем случае это класс Figure. Любая фигура будет иметь поля данных x и y – координаты фигуры на экране.
Класс Dot (“точка”) является наследником Figure, поэтому он будет иметь поля данных x и y, наследуемые от Figure. То есть в самом классе Dot задавать эти поля не надо. От Dot мы наследуем класс Circle (“окружность”), поэтому в нём также имеется поля x и y, наследуемые от Figure. Но появляется дополнительное поля данных. У Circle это поле, соответствующее радиусу. Мы назовём его r. Кроме того, для окружности возможна операция изменения радиуса, поэтому в ней может появиться новый метод, обеспечивающий это действие – назовём его setSize (“установить размер”). Класс Ellipse имеет те же поля данных и обеспечивает то же поведение, что и Circle, но в этом классе появляется дополнительное поле данных r2 – длина второй полуоси эллипса, и возможность регулировать значение этого поля. Возможен и другой подход, в некотором роде более логичный: считать эллипс сплюснутой или растянутой окружностью. В этом случае необходимо ввести коэффициент растяжения (aspect ratio). Назовём его k. Тогда эллипс будет характеризоваться радиусом r и коэффициентом растяжения k. Метод, обеспечивающий изменение k, назовём stretch (“растянуть”). Обратим внимание, что исходя из выбранной логики действий метод scale должен приводить к изменению поля r и не затрагивать поле k – поэтому эллипс будет масштабироваться без изменения формы.
Каждый из классов этой ветви иерархии фигур можно считать описанием “усложнённой точки”. При этом важно, что любой объект такого типа можно считать “точкой, которую усложнили”. Грубо говоря, считать, что круг или эллипс – это такая “жирная точка”. Аналогичным образом Ellipse является “усложнённой окружностью”
Аналогично, класс Square наследует поля x и y, но в нём добавляется поле, соответствующее стороне квадрата. Мы назовём его a. У Triangle в качестве новых, не унаследованных полей данных могут выступать координаты вершин треугольника; либо координаты одной из вершин, длины прилегающих к ней сторон и угол между ними, и так далее.
Как располагать классы иерархии, базовый класс внизу а наследники вверху, образуя ветви дерева наследования, или наоборот, базовый класс вверху а наследники внизу, образуя “корни” дерева наследования – принципиального значения не имеет. По-видимому, на начальном этапе развития объектного программирования применялся первый вариант, почему базовый класс, лежащий в основе иерархии, и получил такое название. Такой вариант выбран в данном учебном пособии, поскольку именно он используется в NetBeans Enterprise Pack. Хотя настоящее время чаще используют второй вариант, когда базовый класс располагают сверху.
В литературе по объектному программированию часто встречается следующий критерий: “если имеются классы A1 и A2, и можно сказать, что A2 является частным случаем A1, то A2 должен описываться как потомок A1”. Данный критерий не совсем корректен.