Отношения между классами
Большая часть классов приложения связаны между собой. В этом разделе рассмотрим какие бывают отношения между классами в Java.
1. IS-A отношения
В ООП принцип IS-A основан на наследовании классов или реализации интерфейсов. Например, если класс HeavyBox наследует Box , мы говорим, что HeavyBox является Box ( HeavyBox IS-A Box ). Или другой пример — класс Lorry расширяет класс Car . В этом случае Lorry IS-A Car .
То же самое относится и к реализации интерфейсов. Если класс Transport реализует интерфейс Moveable , то они находятся в отношении Transport IS-A Moveable .
2. HAS-A отношения
HAS-A отношения основаны на использовании. Выделяют три варианта отношения HAS-A: ассоциация, агрегация и композиция.
Начнем с ассоциации. В этих отношениях объекты двух классов могут ссылаться друг на друга. Например, класс Horse HAS-A Halter если код в классе Horse содержит ссылку на экземпляр класса Halter :
Ассоциация
public class Halter <>
public class Horse
Агрегация и композиция являются частными случаями ассоциации. Агрегация — отношение когда один объект является частью другого. А композиция — еще более тесная связь, когда объект не только является частью другого объекта, но и вообще не может принадлежать другому объекту. Разница будет понятна при рассмотрении реализации этих отношений.
Агрегация
Объект класса Halter создается извне Horse и передается в конструктор для установления связи. Если объект класса Horse будет удален, объект класса Halter может и дальше использоваться, если, конечно, на него останется ссылка:
public class Horse < private Halter halter; public Horse(Halter halter) < this.halter = halter; >>
Композиция
Теперь посмотрим на реализацию композиции. Объект класса Halter создается в конструкторе, что означает более тесную связь между объектами. Объект класса Halter не может существовать без создавшего его объекта Horse:
public class Horse < private Halter halter; public Horse() < this.halter = new Halter(); >>
Композиция и наследование в Java — в чём разница?
Мы знаем, что и наследование, и композиция, дают нам возможность повторно использовать код на Java. Однако делают они это по-разному. Об этом и поговорим.
Главное отличие между композицией и наследованием заключается в том, что композиция даёт возможность переиспользовать код без расширения существующего класса, как это происходит в случае с наследованием. Не менее важно и то, что композиция позволяет нам выполнять повторное использование кода даже из final-класса, в то время как унаследоваться от него мы не сможем. Есть и ещё кое-что: при композиции допускается использование кода из нескольких разных классов, а вот с наследованием это не сработает, ведь в языке программирования Java множественное наследование не поддерживается (правда, мы можем это сделать в C++).
Таким образом, в Java рекомендуется использовать преимущественно композицию, а не наследование. Почему? Ну, во-первых, это советует Джошуа Блох в книге «Effective Java». Книга, кстати, представляет собой прекрасный источник полезных рекомендаций для Java-разработчика. Во-вторых, ряд убедительных аргументов представлен в статье «5 Reasons to Use Composition over Inheritance in Java and OOP» (рекомендуется к прочтению).
Композиция и наследования: пробуем разобраться
Итак, давайте посмотрим на отличия более подробно, изучив каждый пункт тщательнее, но исключив скучные детали.
Гибкость
Одно из первых отличий связано с гибкостью кода. Используя наследование, мы должны описывать, какой класс расширяем, причём мы не сможем заменить его в процессе выполнения программы. Иначе обстоит дело с композицией: мы можем определить используемый тип, а он, в свою очередь, может включать несколько разных реализаций. В результате композиция предоставляет нам больше гибкости.
Ограниченное повторное применение кода при наследовании
Мы уже упомянули, что унаследоваться в Java возможно лишь от одного класса, т. е. мы можем повторно использовать только один класс. Если же нам нужна функциональность нескольких классов, необходимо использовать композицию. Например, если код должен использовать аутентификацию, то нужно унаследовать класс Authenificator, а при авторизации — Autorizer и т. п. Но т. к. множественное наследование не поддерживается, нам опять остаётся композиция.
Юнит-тесты
Следующий важнейший аргумент заключается в том, что классы, которые расширены посредством композиции, тестировать легче, ведь всегда можно предоставить для используемого класса заглушку. Что касается наследования, то нам для тестирования потребуется родительский класс, ведь его заменить заглушкой не выйдет.
Final-классы
Очередное ограничение наследования — нельзя расширить final-классы. В языке программирования Java отсутствует возможность унаследования от final-классов, в результате чего мы опять приходим к тому, что для повторного использования кода больше подходит композиция.
Инкапсуляция
Теперь поговорим об отношении композиции и наследования к инкапсуляции. С одной стороны, можно сказать, что обе техники дают возможность повторно использовать код. Так-то оно так, но наследование нарушает принцип инкапсуляции, и всё дело в том, что подкласс имеет зависимость от поведения класса-родителя. Если же родительский класс поменяет свой поведение, это отразится и на его потомках. Например, если классы будут плохо документированы, а класс-потомок будет неправильно использовать родительский класс, при любом изменении класса-родителя функциональность потомка будет поломана. Об этом, кстати, можно почитать в уже упомянутой выше книге «Effective Java», в главах 16 и 17.
Послесловие
Пожалуй, это всё, что хотелось бы рассказать об отличиях наследования и композиции. Да, обе техники служат одной цели — для повторного применения протестированных и проверенных участков Java-кода. Только вот делают они это по-разному. Композиция даёт нам возможность защитить повторно используемый класс от клиентов, в то время как наследование это не гарантирует. Но несмотря на это, в некоторых случаях наследование просто необходимо. Например, если вы создаёте классы из одного семейства.
Pro Java
Механизм построения нового класса из объектов существующих классов называется композицией (composition).
И мы это уже не раз делали, но просто не знали что это так называется :). Например в паттерне Builder мы внедрили объекты класса String (а String – это класс), как поля класса Contact. Точно так же мы могли бы использовать и объекты классов которые мы создали сами. Хотя по существу при композиции полями класса являются ссылки на объекты определенных классов. И мы можем использовать готовую функциональность этих классов. Например:
Вывод данной программы:
В этом примере видно что в наш класс MyClass мы включили два других класса как поля, а по существу как ссылки на объекты этих классов: String и MyString. Соответственно мы создали два поля ссылающихся на эти классы str и mystr, а затем в методе main() мы создали объект класса MyClass и использовали функциональность этих объектов, которые вывели нам результат.
Обратите внимание что в классе MyString мы переопределили метод класса Object toString(), что позволило нам вывести значение поля myStr, которое кстати тоже является ссылкой на объект.
Композиция позволяет повторно использовать существующий код или функциональность классов.
Но в случае композиции мы ни как не можем повлиять на эту функциональность используемых классов. Мы можем только использовать ее, но не изменить поведение, так как нам может быть нужно. Но есть и другой способ который гораздо интереснее когда новый класс создается как специализация уже существующего класса . Взяв существующий класс за основу, вы добавляете к нему свой код без изменения существующего класса. Этот механизм называется наследованием (inheritance) . И далее мы переходим к изучению наследования в Java.
Что такое Композиция? Пример Композиции в Java
Композиция является одним из методов проектирования, который реализовывает отношение типа has-a в классах. Мы можем использовать наследование в Java или композицию для повторного использования кода.
Композиция в Java достигается за счет использования переменных экземпляра, который ссылается на другие объекты.
Небольшой пример в теории: Person has a (имеет) Job.
А теперь практика на java:
package ua . com . prologistic . composition ;
public class Job <
private String role ;
private long salary ;
private int id ;
public String getRole ( ) <
return role ;
public void setRole ( String role ) <
this . role = role ;
public long getSalary ( ) <
return salary ;
public void setSalary ( long salary ) <
this . salary = salary ;
public int getId ( ) <
public void setId ( int id ) <
this . id = id ;
package ua . com . prologistic . composition ;
public class Person <
//используем отношение has-a
private Job job ;
public Person ( ) <
this . job = new Job ( ) ;
job . setSalary ( 1000L ) ;
public long getSalary ( ) <
return job . getSalary ( ) ;
А теперь протестируем: используем объект Person и получаем зарплату (salary)
public class TestPerson < public static void main ( String [ ] args ) < Person person = new Person ( ) ; long salary = person . getSalary ( ) ;
Обратите внимание, что тестовая программа TestPerson не зависит от каких-либо изменений в объекте Job. Если вам нужно реализовать взаимодействие двух классов и вы хотите повторно использовать код, то лучшим выходом будет использование композиции вместо наследования.
Преимущества использования композиции в том, что мы можем управлять видимостью другого объекта для клиентских классов и повторно использовать только то, что нам нужно.
Кроме того, если есть какие-либо изменения в другой реализации класса, например, если метод getSalary() начнет возвращать строку, то мы должны изменить класс Person, а не классы клиента.
Композиция в Java позволяет создавать back-end класс, когда это необходимо. Например, мы можем изменить getSalary() метод класса Person для инициализации объекта Job.
Больше полезных статей!
3 thoughts to “Что такое Композиция? Пример Композиции в Java”
Здравствуйте. Объясните пожалуйста следующий момент.
Вот определение Композиции которое представлено в Википедии: Композиция — более строгий вариант агрегации. Известна также как агрегация по значению.
Композиция имеет жёсткую зависимость времени существования экземпляров класса контейнера и экземпляров содержащихся классов. Если контейнер будет уничтожен, то всё его содержимое будет также уничтожено. В принципе это созвучно с тем, как излагают данный вопрос Джим Арлоу и Айла Нейштадт в книге UML2 :
Композиция — это строгая форма агрегации:
1) Одновременно части могут принадлежать только одному композиту — совместное владение частями невозможно.
2) композит обладает исключительной ответственностью за все свои части; это значит что он отвечает за их создание и уничтожение
3) композит может высвобождать части, передавая ответственность за них другому объекту
4) в случае уничтожения композита он должен уничтожить все свои части или передать ответственность за них другому объекту. Вопрос в следующем: данный пример показывает свойства композиции или все таки агрегации, Ведь фактически пример не отвечает ни одному вышеперечисленному пункту. Или я что — то не так понимаю. Заранее благодарен.
Эдуард :
Пример показывает как именно используется композиция , время жизни объекта job зависит от времени жизни экземпляра класса , а вот при агрегации нет , объект при агрегации может продолжать существовать после уничтожения экземпляра , дело в том, что при агрегации объект передается
как параметр.
Так при реассайне ссылки типа Job на объект, данный объект будет продолжать существование вне зависимости, что с Person дальше будет. Следовательно это агрегация.
А композиция была бы если определить внутренний нестатический класс Job и через this.new создать объект Job, который хранит ссылку на объект обрамляющего класса Person. И теперь, если что-то случиться с Person, Job также «потушится».