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

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

Композиция – это описание объекта как состоящего из других объектов (отношение агрегации, или включения как составной части) или находящегося с ними в отношении ассоциации (объединения независимых объектов). Если наследование характеризуется отношением “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, это не всегда помогает. Например, если объект должен помещаться в список. Тем не менее часто использование композиции является гораздо более удачным решением, чем множественное наследование.

Таким образом, сочетание множественного наследования интерфейсов и композиции в подавляющем большинстве случаев является полноценной альтернативой множественному наследованию классов.

Краткие итоги по главе 8

ü Интерфейсы используются для написания полиморфного кода для классов, лежащих в различных, никак не связанных друг с другом иерархиях.

ü Интерфейсы описываются аналогично абстрактным классам. Так же, как абстрактные классы, они не могут иметь экземпляров. Но, в отличие от абстрактных классов, интерфейсы не могут иметь полей данных (за исключением констант), а также реализации никаких своих методов.

ü Интерфейс определяет методы, которые должны быть реализованы классом-наследником этого интерфейса.

ü Хотя экземпляров типа интерфейс не бывает, могут существовать переменные типа интерфейс. Такая переменная - это ссылка. Она дает возможность ссылаться на объект, чей класс реализует данный интерфейс.

ü С помощью переменной типа интерфейс разрешается вызывать только методы, декларированные в данном интерфейсе, а не любые методы данного объекта.

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

ü Наследование характеризуется отношением “is-a” (“это есть”, “является”), а композиция - отношением “has-a” (“имеет в своём составе”, “состоит из”) и “use-a” (“использует”).

ü Сочетание множественного наследования интерфейсов и композиции в подавляющем большинстве случаев является полноценной альтернативой множественному наследованию классов.

Типичные ошибки:

  • После настройки ссылки, хранящейся в переменной типа интерфейс, на объект какого-либо класса, реализующего этот интерфейс, пытаются вызвать поле или метод этого объекта, не объявленные в интерфейсе. Для такого вызова требуется приведение типа, причём до него рекомендуется проверить соответствие объекта этому типу.

Задания

Наши рекомендации