Не очень понятно про оболочечные Данные,ячейки памяти могут наверно размер менять,какое это значение те же операции только ячейки больше,по скорости тоже самое |
Наследование: проблемы и альтернативы. Интерфейсы. Композиция
8.3. Пример на использование интерфейсов
Приведем пример абстрактного класса, являющегося наследником Figure, и реализующего указанный выше интерфейс IScalable:
package figures_pkg; public abstract class ScalableFigure extends Figure implements IScalable { private int size; public int getSize() { return size; } public void setSize(int size) { this.size=size; } }
В качестве наследника приведем код класса Circle:
package figures_pkg; import java.awt.*; public class Circle extends ScalableFigure { public Circle(Graphics g,Color bgColor, int r){ setGraphics(g); setBgColor(bgColor); setSize(r); } public Circle(Graphics g,Color bgColor){ setGraphics(g); setBgColor(bgColor); setSize( (int)Math.round(Math.random()*40) ); } public void show(){ Color oldC=getGraphics().getColor(); getGraphics().setColor(Color.BLACK); getGraphics().drawOval(getX(),getY(),getSize(),getSize()); getGraphics().setColor(oldC); } public void hide(){ Color oldC=getGraphics().getColor(); getGraphics().setColor(getBgColor()); getGraphics().drawOval(getX(),getY(),getSize(),getSize()); getGraphics().setColor(oldC); } };
Приведем пример наследования интерфейсом от интерфейса:
package figures_pkg; public interface IStretchable extends IScalable{ double getAspectRatio(); void setAspectRatio(double aspectRatio); int getWidth(); void setWidth(int width); int getHeight(); void setHeight(int height); }
Интерфейс IScalable описывает методы объекта, способного менять свой размер (size). При этом отношение ширины к высоте ( AspectRatio – отношение сторон) у фигуры не меняется. Интерфейс IStretchable описывает методы объекта, способного менять не только свой размер, но и "растягиваться" – изменять отношение ширины к высоте ( AspectRatio ).
К интерфейсам применимы как оператор instanceof, так и приведение типов. Например, фрагмент кода для изменения случайным образом размера объекта с помощью интерфейса IScalable может выглядеть так:
Object object; ... object= Circle(...);//конструктор создает окружность ... if(object instanceof IScalable){ ((IScalable) object).setSize( (int)(Math.random()*80) ); }
Все очень похоже на использование класса ScalableFigure:
Figure figure; ... figure = Circle(...);//конструктор создает окружность ... if( figure instanceof IScalable){ figure.hide(); ((IScalable)figure).setSize((int)(Math.random()*80)); figure.show(); }
Но если во втором случае переменная figure имеет тип Figure, то есть связанный с ней объект обязан быть фигурой, то на переменную object такое ограничение не распространяется. Зато фигуру можно скрывать и показывать, а для переменной типа Object это можно делать только после проверки, что object является экземпляром (то есть instanceof ) класса Figure.
Аналогичный код можно написать в случае использования переменной типа IScalable:
IScalable scalableObj; ... scalableObj = Circle(...);//конструктор создает окружность ... scalableObj.setSize((int)(Math.random()*80));
Заметим, что присваивание Object object= Circle(...) разрешено, так как Circle – наследник Object. Аналогично, присваивание Figure figure = Circle(...) разрешено, так как Circle – наследник Figure. И, наконец, присваивание scalableObj =Circle(...) разрешено, так как Circle – наследник IScalable.
При замене в коде Circle(...) на Dot(...) мы бы получили правильный код в первых двух случаях, а вот присваивание scalableObj = Dot (...); вызвало бы ошибку компиляции, так как класс Dot не реализует интерфейс IScalable, то есть не является его потомком.
8.4. Композиция как альтернатива множественному наследованию
Как уже говорилось ранее, наследование относится к одному из важных аспектов, присущих объектам – поведению. Причем оно относится не к самим объектам, а к классам. Но имеется и другой аспект, присущий объектам – внутреннее устройство. При наследовании этот аспект скорее скрывается, чем подчеркивается: наследники должны быть устроены так, чтобы отличие в их устройстве не сказывалось на абстракциях их поведения.
Композиция – это описание объекта как состоящего из других объектов (отношение агрегации, или включения как составной части) или находящегося с ними в отношении ассоциации (объединения независимых объектов). Если наследование характеризуется отношением "is-a" ("это есть", "является"), то композиция характеризуется отношением "has-a" ("имеет в своем составе", "состоит из") и "use-a" ("использует").
Важность использования композиции связана с тем, что она позволяет объединять отдельные части в единую более сложную систему. Причем описание и испытание работоспособности отдельных частей можно делать независимо от других частей, а тем более от всей сложной системы. Таким образом, композиция – это объединение частей в единую систему.
В качестве примера агрегации можно привести классический пример – автомобиль. Он состоит из корпуса, колес, двигателя, карбюратора, топливного бака и т.д. Каждая из этих частей, в свою очередь, состоит из более простых деталей. И так далее, до того уровня, когда деталь можно считать единым целым, не включающий в себя другие объекты.
Шофер также является неотъемлемой частью автомобиля, но вряд ли можно считать, что автомобиль состоит из шофера и других частей. Но можно говорить, что у автомобиля обязательно должен быть шофер. Либо говорить, что шофер использует автомобиль. Отношение объекта "автомобиль" и объекта "шофер" гораздо слабее, чем агрегация, но все-таки весьма сильное – это композиция в узком смысле этого слова.
И, наконец, отношение автомобиля с находящимися в нем сумками или другими посторонними предметами – это ассоциация. То есть отношение независимых предметов, которые на некоторое время образовали единую систему. В таких случаях говорят, что автомобиль используют для того, чтобы отвезти предметы по нужному адресу.
С точки зрения программирования на Java композиция любого вида - это наличие в объекте поля ссылочного типа. Вид композиции определяется условиями создания связанного с этой ссылочной переменной объекта и изменения этой ссылки. Если такой вспомогательный объект создается одновременно с главным объектом и "умирает" вместе с ним – это агрегация. В противном случае это или композиция в узком смысле слова, или ассоциация.
Композиция во многих случаях может служить альтернативой множественному наследованию, причем именно в тех ситуациях, когда наследование интерфейсов "не работает". Это бывает в случаях, когда надо унаследовать от двух или более классов их поля и методы.
Приведем пример. Пусть у нас имеются классы Car ("Автомобиль") , класс Driver ("Шофер") и класс Speed ("Скорость"). И пусть это совершенно независимые классы. Зададим класс MovingCar ("движущийся автомобиль") как
public class MovingCar extends Car{ Driver driver; Speed speed; …}
Особенностью объектов MovingCar будет то, что они включают в себя не только особенности поведения автомобиля, но и все особенности объектов типа Driver и Speed. Например, автомобиль "знает" своего водителя: если у нас имеется объект movingCar, то movingCar.driver обеспечит доступ к объекту "водитель" (если, конечно, ссылка не равна null ). В результате чего можно будет пользоваться общедоступными (и только!) методами этого объекта. То же относится к полю speed. И нам не надо строить гибридный класс-монстр, в котором от родителей Car, Driver и Speed унаследовано по механизму множественного наследования нечто вроде машино-кентавра, где шофера скрестили с автомобилем. Или заниматься реализацией в классе-наследнике интерфейсов, описывающих взаимодействие автомобиля с шофером и измерение/задание скорости.
Но у композиции имеется заметный недостаток: для получившегося класса имеется существенное ограничение при использовании полиморфизма. Ведь он не является наследником классов Driver и Speed. Поэтому полиморфный код, написанный для объектов типа Driver и Speed, для объектов типа MovingCar работать не будет. И хотя он будет работать для соответствующих полей movingCar.driver и movingCar.speed, это не всегда помогает. Например, если объект должен помещаться в список. Тем не менее часто использование композиции является гораздо более удачным решением, чем множественное наследование.
Таким образом, сочетание множественного наследования интерфейсов и композиции в подавляющем большинстве случаев является полноценной альтернативой множественному наследованию классов.
Краткие итоги
- Интерфейсы используются для написания полиморфного кода для классов, лежащих в различных, никак не связанных друг с другом иерархиях.
- Интерфейсы описываются аналогично абстрактным классам. Так же, как абстрактные классы, они не могут иметь экземпляров. Но, в отличие от абстрактных классов, интерфейсы не могут иметь полей данных (за исключением констант), а также реализации никаких своих методов.
- Интерфейс определяет методы, которые должны быть реализованы классом-наследником этого интерфейса.
- Хотя экземпляров типа интерфейс не бывает, могут существовать переменные типа интерфейс. Такая переменная - это ссылка. Она дает возможность ссылаться на объект, чей класс реализует данный интерфейс.
- С помощью переменной типа интерфейс разрешается вызывать только методы, декларированные в данном интерфейсе, а не любые методы данного объекта.
- Композиция – это описание объекта как состоящего из других объектов (отношение агрегации, или включения как составной части) или находящегося с ними в отношении ассоциации (объединения независимых объектов). Композиция позволяет объединять отдельные части в единую более сложную систему.
- Наследование характеризуется отношением "is-a" ("это есть", "является"), а композиция - отношением "has-a" ("имеет в своем составе", "состоит из") и "use-a" ("использует").
- Сочетание множественного наследования интерфейсов и композиции в подавляющем большинстве случаев является полноценной альтернативой множественному наследованию классов.
Типичные ошибки:
- После настройки ссылки, хранящейся в переменной типа интерфейс, на объект какого-либо класса, реализующего этот интерфейс, пытаются вызвать поле или метод этого объекта, не объявленные в интерфейсе. Для такого вызова требуется приведение типа, причем до него рекомендуется проверить соответствие объекта этому типу.
Задания
- Написать реализацию класса Square – наследника ScalableFigure. Класс должен располагаться в пакете AdditionalFigures.
- В пакете AdditionalFigures задать интерфейс IScalable.
- В качестве класса, реализующего этот интерфейс, написать абстрактный класс StretchableFigure. Класс должен располагаться в пакете AdditionalFigures.
- Написать реализацию класса Rectangle – наследника StretchableFigure. Класс должен располагаться в пакете AdditionalFigures.
- Написать приложение, в котором в зависимости от выбранной радиокнопки создается и отрисовывается на панели в произвольном месте, не выходящем за пределы панели, точка, окружность, квадрат или прямоугольник. По нажатию на кнопки "Создать объект", "show", "hide", "moveTo" должны выполняться соответствующие методы для последнего созданного объекта.
- Усложнить копию данного приложения, добавив на форму компонент с прокручивающимся или выпадающим списком с именами объектов. Имя объекта должно состоять из имени, соответствующего типу, и порядкового номера ( dot1, circle3 и т.п.). По нажатию на кнопки "Создать объект", "show", "hide", "moveTo" должны выполняться соответствующие методы для объекта, выделенного в списке.
- Добавить кнопки "Изменить размер" и "Растянуть объект". В случае, если объект поддерживает интерфейс ScalableFigure, по нажатию первой из них он должен менять размер. Если он поддерживает интерфейс , по нажатию второй он должен растягиваться или сплющиваться в зависимости от значения в соответствующем пункте ввода.
- В пакете AdditionalFigures написать интерфейс IBordered, обеспечивающий поддержку методов, необходимых для рисования границы ( border ) заданной ширины и цвета вокруг графического объекта. Реализовать этот интерфейс в классах BorderedCircle, BorderedSquare, BorderedRectangle.