Достаточно часто требуется совмещать в объекте поведение, характерное для двух или более независимых иерархий. Но еще чаще требуется писать единый полиморфный код для объектов из таких иерархий в случае, когда эти объекты обладают схожим поведением. Как мы знаем, за поведение объектов отвечают методы. Это значит, что в полиморфном коде требуется для объектов из разных классов вызывать методы, имеющие одинаковую сигнатуру, но разную реализацию. Унарное наследование, которое мы изучали до сих пор, и при котором у класса может быть только один прародитель, не обеспечивает такой возможности. При унарном наследовании нельзя ввести переменную, которая бы могла ссылаться на экземпляры из разных иерархий, так как она должна иметь тип, совместимый с базовыми классами этих иерархий.
В C++ для решения данных проблем используется множественное наследование. Оно означает, что у класса может быть не один непосредственный прародитель, а два или более. В этом случае проблема совместимости с классами из разных иерархий решается путем создания класса, наследующего от необходимого
Но при множественном наследовании классов возникает ряд трудно
В частности, так называемое ромбовидное наследование, когда у класса A наследники B и C, а от них наследуется класс D. Поэтому класс D получает поля и методы, имеющиеся в классе A, в удвоенном количестве – один комплект по линии родителя B, другой – по линии родителя C.
Конечно, в C++ имеются средства решать указанные проблемы. Например, проблемы ромбовидного наследования снимаются использованием так называемых виртуальных классов, благодаря чему при ромбовидном наследовании потомкам достается только один комплект членов класса A, и различения имен из разных ветвей класса с помощью имен классов. Но в результате получается заметное усложнение логики работы с классами и объектами, вызывающее
Интерфейсы являются важнейшими
Интерфейсы являются специальной разновидностью полностью
Отсутствие в интерфейсах полей данных и реализованных методов снимает почти все проблемы Collection. Аналогично, все классы , и т.д.
Декларация интерфейса очень похожа на декларацию класса:
МодификаторВидимости interface ИмяИнтерфейса
extends ИмяИнтерфейса1, ИмяИнтерфейса2,..., ИмяИнтерфейсаN{
декларация констант;
декларация заголовков методов;
}
В качестве public – общая видимость, либо модификатор должен отсутствовать, в этом случае обеспечивается видимость по умолчанию - пакетная. В списке прародителей, расширением которых является данный интерфейс, указываются прародительские интерфейсы ИмяИнтерфейса1, ИмяИнтерфейса2 и так далее. Если список прародителей пуст, в отличие от классов, интерфейс не имеет прародителя.
Для имен интерфейсов в Java нет специальных правил, за исключением того, что для них, как и для других I (от слова Interface ), чтобы их имена легко отличать от имен классов.
Объявление константы осуществляется почти так же, как в классе:
МодификаторВидимости Тип ИмяКонстанты = значение;
В качестве необязательного public. Либо модификатор должен отсутствовать - но при этом видимость также считается public, а не пакетной. Еще одним отличием от декларации в классе является то, что при задании в интерфейсе все поля автоматически считаются окончательными (модификатор final ), т.е. без права изменения, и к тому же являющимися static ). Сами модификаторы static и final при этом ставить не надо.
Декларация метода в интерфейсе осуществляется очень похоже на декларацию
МодификаторВидимости Тип ИмяМетода(списокПараметров) throws списокИсключений;
В качестве public, либо модификатор должен отсутствовать. При этом видимость также считается public, а не пакетной. В списке исключений через запятую перечисляются типы Exception ), которые может возбуждать метод. Часть throws списокИсключений является необязательной. При задании в интерфейсе все методы автоматически считаются общедоступными ( public ) абстрактными ( abstract ) методами объектов.
Пример задания интерфейса:
package figures_pkg;
public interface IScalable {
public int getSize();
public void setSize(int newSize);
}
Класс можно наследовать от одного extends используется зарезервированное слово implements - реализует. Говорят, что класс-наследник интерфейса реализует соответствующий интерфейс, так как он обязан реализовать все его методы. Это гарантирует, что объекты для любых классов, наследующих некий интерфейс, могут вызывать методы этого интерфейса. Что позволяет писать полиморфный код для объектов из разных
Уточнение: в абстрактных классах, реализующих интерфейс, реализации методов может не быть – наследуется декларация
Замечание: Интерфейс также может реализовывать (implements) другой интерфейс, если в том при задании типа использовался шаблон ( generics, template). Но эта тема выходит за пределы данного учебного пособия.
В наследнике класса, реализующего интерфейс, можно переопределить методы этого интерфейса. Повторное указание в списке родителей интерфейса, который уже был унаследован кем-то из прародителей, запрещено.
Реализации у сущности типа интерфейс не бывает, как и для
Правила совместимости таковы: переменной типа интерфейс можно присваивать ссылку на объект любого класса, реализующего этот интерфейс.
С помощью переменной типа интерфейс разрешается вызывать только методы, декларированные в данном интерфейсе, а не любые методы данного объекта. Если, конечно, не использовать приведение типа.
Основное назначение переменных
public (в том числе без явного указания). Не разрешено использовать модификаторы видимости кроме public.abstract (хотя и являются абстрактными по умолчанию), static, native , synchronized, final, private, protected.Кроме указанных отличий имеется еще одно, связанное с проблемой
Аналогичная ситуация может возникнуть и с константами, хотя, в отличие от методов, реально этого почти никогда не происходит.
Для использования констант из разных интерфейсов решением является квалификация имени константы именем соответствующего интерфейса – все аналогично
public interface I1 {
Double PI=3.14;
}
public interface I2 {
Double PI=3.1415;
}
class C1 implements I1,I2 {
void m1(){
System.out.println("I1.PI="+ I1.PI);
System.out.println("I2.PI="+ I2.PI);
};
}
Но для методов такой способ различения имен запрещен. Поэтому наследовать от двух и более интерфейсов методы, имеющие совпадающие сигнатуры, но различающиеся контракты, нельзя. Если сигнатуры различаются, проблем нет, и используется перегрузка. Если контракты совпадают или совместимы, в классе можно реализовать один метод, который будет реализацией для всех интерфейсов, в которых продекларирован метод с таким контрактом.
Методы, объявленные в интерфейсе, могут возбуждать
В том случае, если класс A2 на уровне абстракций ведет себя так же, как класс A1, но кроме того обладает дополнительными особенностями поведения, следует использовать наследование. То есть считать класс A2 наследником класса A1. Действует правило " A2 есть A1 " (A2 is a A1).
Если для нескольких классов A1,B1,… из разных иерархий можно на уровне абстракций выделить общность поведения, следует задать интерфейс I, который описывает эти абстракции поведения. А классы задать как наследующие этот интерфейс. Таким образом, действует правило " A1, B1,… есть I " (A1, B1,… is a I).
Если класс-прародитель унаследовал какой-либо интерфейс, все его потомки также будут наследовать этот интерфейс.
Приведем пример , и реализующего указанный выше интерфейс 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();
}
Но если во втором случае переменная имеет тип , то есть связанный с ней объект обязан быть фигурой, то на переменную object такое ограничение не распространяется. Зато фигуру можно скрывать и показывать, а для переменной типа Object это можно делать только после проверки, что object является экземпляром (то есть instanceof ) класса .
Аналогичный код можно написать в случае использования переменной типа IScalable:
IScalable scalableObj; ... scalableObj = Circle(...);//конструктор создает окружность ... scalableObj.setSize((int)(Math.random()*80));
Заметим, что присваивание Object object= Circle(...) разрешено, так как Circle – наследник Object. Аналогично, присваивание разрешено, так как Circle – наследник . И, наконец, присваивание scalableObj =Circle(...) разрешено, так как Circle – наследник IScalable.
При замене в коде Circle(...) на Dot(...) мы бы получили правильный код в первых двух случаях, а вот присваивание scalableObj = Dot (...); вызвало бы ошибку компиляции, так как класс Dot не реализует интерфейс IScalable, то есть не является его потомком.
Как уже говорилось ранее, наследование относится к одному из важных аспектов, присущих объектам – поведению. Причем оно относится не к самим объектам, а к классам. Но имеется и другой аспект, присущий объектам – внутреннее устройство. При наследовании этот аспект скорее скрывается, чем подчеркивается: наследники должны быть устроены так, чтобы отличие в их устройстве не сказывалось на абстракциях их поведения.
Композиция – это описание объекта как состоящего из других объектов (отношение
Важность использования композиции связана с тем, что она позволяет объединять отдельные части в единую более сложную систему. Причем описание и испытание работоспособности отдельных частей можно делать независимо от других частей, а тем более от всей сложной системы. Таким образом, композиция – это объединение частей в единую систему.
В качестве примера
Шофер также является неотъемлемой частью автомобиля, но вряд ли можно считать, что автомобиль состоит из шофера и других частей. Но можно говорить, что у автомобиля обязательно должен быть шофер. Либо говорить, что шофер использует автомобиль. Отношение объекта "автомобиль" и объекта "шофер" гораздо слабее, чем агрегация, но все-таки весьма сильное – это композиция в узком смысле этого слова.
И, наконец, отношение автомобиля с находящимися в нем сумками или другими посторонними предметами – это ассоциация. То есть отношение независимых предметов, которые на некоторое время образовали единую систему. В таких случаях говорят, что автомобиль используют для того, чтобы отвезти предметы по нужному адресу.
С точки зрения программирования на 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, это не всегда помогает. Например, если объект должен помещаться в список. Тем не менее часто использование композиции является гораздо более удачным решением, чем
Таким образом, сочетание
Square – наследника ScalableFigure. Класс должен располагаться в пакете AdditionalFigures.AdditionalFigures задать интерфейс IScalable.StretchableFigure. Класс должен располагаться в пакете AdditionalFigures.Rectangle – наследника StretchableFigure. Класс должен располагаться в пакете AdditionalFigures.dot1, circle3 и т.п.). По нажатию на кнопки "Создать объект", "show", "hide", "moveTo" должны выполняться соответствующие методы для объекта, выделенного в списке.ScalableFigure, по нажатию первой из них он должен менять размер. Если он поддерживает интерфейс , по нажатию второй он должен растягиваться или сплющиваться в зависимости от значения в соответствующем пункте ввода.AdditionalFigures написать интерфейс IBordered, обеспечивающий поддержку методов, необходимых для рисования границы ( border ) заданной ширины и цвета вокруг графического объекта. Реализовать этот интерфейс в классах BorderedCircle, BorderedSquare, BorderedRectangle.Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.