Смекни!
smekni.com

Общие представления о языке Java 5 (стр. 55 из 68)

Отличия интерфейсов от классов. Проблемы наследования интерфейсов

· Не бывает экземпляров типа интерфейс, то есть экземпляров интерфейсов, реализующих тип интерфейс.

· Список элементов интерфейса может включать только методы и константы. Поля данных использовать нельзя.

· Элементы интерфейса всегда имеют тип видимости 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).

Множественное наследование в Java может быть двух видов:

- Только от интерфейсов, без наследования реализации.

- От класса и от интерфейсов, с наследованием реализации от прародительского класса.

Если класс-прародитель унаследовал какой-либо интерфейс, все его потомки также будут наследовать этот интерфейс.

Пример на использование интерфейсов

Приведём пример абстрактного класса, являющегося наследником 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, то есть не является его потомком.

Композиция как альтернатива множественному наследованию

Как уже говорилось ранее, наследование относится к одному из важных аспектов, присущих объектам – поведению. Причём оно относится не к самим объектам, а к классам. Но имеется и другой аспект, присущий объектам – внутреннее устройство. При наследовании этот аспект скорее скрывается, чем подчёркивается: наследники должны быть устроены так, чтобы отличие в их устройстве не сказывалось на абстракциях их поведения.

Композиция – это описание объекта как состоящего из других объектов (отношение агрегации, или включения как составной части) или находящегося с ними в отношении ассоциации (объединения независимых объектов). Если наследование характеризуется отношением “is-a” (“это есть”, “является”), то композиция характеризуется отношением “has-a” (“имеет в своём составе”, “состоит из”) и “use-a” (“использует”).

Важность использования композиции связана с тем, что она позволяет объединять отдельные части в единую более сложную систему. Причём описание и испытание работоспособности отдельных частей можно делать независимо от других частей, а тем более от всей сложной системы. Таким образом, композиция – это объединение частей в единую систему.

В качестве примера агрегации можно привести классический пример – автомобиль. Он состоит из корпуса, колёс, двигателя, карбюратора, топливного бака и т.д. Каждая из этих частей, в свою очередь, состоит из более простых деталей. И так далее, до того уровня, когда деталь можно считать единым целым, не включающий в себя другие объекты.

Шофёр также является неотъемлемой частью автомобиля, но вряд ли можно считать, что автомобиль состоит из шофёра и других частей. Но можно говорить, что у автомобиля обязательно должен быть шофёр. Либо говорить, что шофёр использует автомобиль. Отношение объекта “автомобиль” и объекта “шофёр” гораздо слабее, чем агрегация, но всё-таки весьма сильное – это композиция в узком смысле этого слова.

И, наконец, отношение автомобиля с находящимися в нём сумками или другими посторонними предметами – это ассоциация. То есть отношение независимых предметов, которые на некоторое время образовали единую систему. В таких случаях говорят, что автомобиль используют для того, чтобы отвезти предметы по нужному адресу.

С точки зрения программирования на Java композиция любого вида - это наличие в объекте поля ссылочного типа. Вид композиции определяется условиями создания связанного с этой ссылочной переменной объекта и изменения этой ссылки. Если такой вспомогательный объект создаётся одновременно с главным объектом и “умирает” вместе с ним – это агрегация. В противном случае это или композиция в узком смысле слова, или ассоциация.