в чем разница между агрегацией и композицией
Просто про наследование, композицию и агрегацию в PHP
К моему удивлению, очень часто возникает непонимание в чем же отличие между наследованием, композицией и агрегацией. Приведу, пару простых примеров, которые без лишней воды продемонстрируют отличия..
Что такое наследование, композиция и агрегация
Композиция и Агрегация является подвидом Ассоциации. Звучит, не понятно, но не все так сложно..
Очень сложно, до свидания
Для, того чтобы было более понятно, я организую всю эту структуру в такой список:
Способы взаимодействия классов
Уже попроще, тк понятна структура терминов. Теперь давайте разберемся с каждым по отдельности
Наследование
Это один из принципов ООП, который подразумевает, что класс наследник, будет иметь все те же свойства и методы, что и в базовом классе. В коде это выглядит так:
Как следует из ключевого слова extend, используется для расширения возможностей базового класса. Со стороны программиста, это может потребоваться для переопределения какого-либо метода или для добавления какой-нибудь новой логики.
Это классика ООП, поэтому думаю с понимаем проблем не должно быть.
Ассоциация
Ассоциация, как следует из название, это способ взаимодействия, когда один класс ассоциируется с другим. Т.е. один класс использует внутри своей реализации свойства или методы объекта другого класса. Просто запомните, что в отличии от наследования, ассоциации не РАСШИРЯЮТ класс, а ИСПОЛЬЗУЮТ объект другого класса. Дальше мы это увидим на примерах, а пока надо упомянуть, что ассоциации бывают двух видов: Композиция и Агрегация.
Композиция
Это ассоциация, при которой используемый объект создается внутри класса. Простой пример:
Тут мы видим, что в конструкторе класса B, создается объект класса A. И дальше используется в методе sayHello.
Преимущество у такого способа, это то, что класс B, управляет временем жизни объекта A. Т.е. при удалении объекта B будет и удален, объект A который был создан внутри B.
Агрегация
Это ассоциация, при которой используемый объект создается вне класса. Простой пример:
Тут мы видим, что создается объект класса A, а заетм он передается извне в конструктор класса B. И дальше используется в методе sayHello.
Заключение
Подведем итоги по способам взаимодействия классов:
Если остались вопросы, пишите в комментариях.
P.S. Обозначение на блок схемах
Совсем забыл про обозначение способов взаимодействия на блок схеме, выглядит это так
Programming stuff
Страницы
понедельник, 3 декабря 2012 г.
Наследование vs Композиция vs Агрегация
Между двумя классами/объектами существует разные типы отношений. Самым базовым типом отношений является ассоциация (association), это означает, что два класса как-то связаны между собой, и мы пока не знаем точно, в чем эта связь выражена и собираемся уточнить ее в будущем. Обычно это отношение используется на ранних этапах дизайна, чтобы показать, что зависимость между классами существует, и двигаться дальше.
Рисунок 1. Отношение ассоциации
Более точным типом отношений является отношение открытого наследования (отношение «является», IS A Relationship), которое говорит, что все, что справедливо для базового класса справедливо и для его наследника. Именно с его помощью мы получаем полиморфное поведение, абстрагируемся от конкретной реализации классов, имея дело лишь с абстракциями (интерфейсами или базовыми классами) и не обращаем внимание на детали реализации.
Рисунок 2. Отношение наследование
И хотя наследование является отличным инструментом в руках любого ОО-программиста, его явно недостаточно для решения всех типов задач. Во-первых, далеко не все отношения между классами определяются отношением «является», а во-вторых, наследование является самой сильной связью между двумя классами, которую невозможно разорвать во время исполнения (это отношение является статическим и, в строготипизированных языках определяется во время компиляции).
В этом случае нам на помощь приходит другая пара отношений: композиция (composition) и агрегация (aggregation). Оба они моделируют отношение «является частью» (HAS-A Relationship) и обычно выражаются в том, что класс целого содержит поля (или свойства) своих составных частей. Грань между ними достаточно тонкая, но важная, особенно в контексте управления зависимостями.
Рисунок 3. Отношение композиции и агрегации
HINT
Пара моментов, чтобы легче запомнить визуальную нотацию: (1) ромбик всегда находится со стороны целого, а простая линия со стороны составной части; (2) закрашенный ромб означает более сильную связь – композицию, незакрашенный ромб показывает более слабую связь – агрегацию.
Разница между композицией и агрегацией заключается в том, что в случае композиции целое явно контролирует время жизни своей составной части (часть не существует без целого), а в случае агрегации целое хоть и содержит свою составную часть, время их жизни не связано (например, составная часть передается через параметры конструктора).
CompositeCustomService для управления своими составными частями использует композицию, а AggregatedCustomService – агрегацию. При этом явный контроль времени жизни обычно приводит к более высокой связанности между целым и частью, поскольку используется конкретный тип, тесно связывающий участников между собой.
С одной стороны, такая жесткая связь может не являться чем-то плохим, особенно когда зависимость является стабильной (см. раздел «Стабильные и изменчивые зависимости» в прошлой заметке). С другой стороны мы можем использовать композицию и контролировать время жизни объекта, не завязываясь на конкретные типы. Например, с помощью абстрактной фабрики:
В данном случае мы не избавляемся от композиции (CustomService все еще контролирует время жизни AbstractRepository), но делает это не напрямую, а с помощью дополнительной абстракции – абстрактной фабрики. Поскольку такой подход требует удвоения количества классов наших зависимостей, то его стоит использовать, когда явный контроль времени жизни является необходимым условием.
Интересной особенностью разных отношений между классами является то, что логичность их использования может зависеть от точки зрения проектировщика, от того, с какой стороны он смотрит на задачу и какие вопросы он задает себе при ее анализе. Именно поэтому одну и ту же задачу можно решить десятком разных способов, при этом в одном случае мы получим сильно связанный дизайн с большим количеством наследования и композиции, а в другом случае – эта же задача будет разбита на более автономные строительные блоки, объединяемые между собой с помощью агрегации.
Например, нашу задачу с сервисами и репозитариями можно решить множеством разных способов. Кто-то скажет, что здесь подойдет наследование и сделает SqlCustomService наследником от AbstractCustomService; другой скажет, что этот подход неверен, поскольку CustomService у нас один, а иерархия должна быть у репозитариев.
Рисунок 4. Наследование vs Агрегация
Каждый вариант приводит к одному и тому же конечному результату, при этом связанность изменяется от очень высокой (при наследовании) к очень слабой (при агрегации).
Заключение
Существует несколько достаточно объективных критериев для определения связности дизайна по диаграмме классов: большие иерархии наследования (глубокие или широкие иерархии), и повсеместное использование композиции, а не агрегации скорее всего говорит о сильно связанном дизайне.
Большое количество наследования говорит о том, что проектировщики забыли о старом добром совете Банды Четырех, который сводится к тому, что следует предпочесть агрегацию наследованию, поскольку первая дает большую гибкость и динамичность во время исполнения.
Обилие же композиции говорит о нарушении Принципа Инверсии Зависимостей, сформулированном Бобом Мартином, которую сейчас можно выразить в терминах агрегации и композиции: предпочитайте агрегацию вместо композиции, поскольку первая стимулирует использование абстракций, а не конкретных классов.
В следующий раз: перейдем к рассмотрению конкретных DI паттернов и начнем с самого популярного из них – с Constructor Injection.
Отличие между Ассоциацией, Композицией и Агрегацией.
Ассоциация.
В ООП объекты находятся в различных отношениях друг с другом. Один объект может использовать функциональность, которую ему предоставляет другой объект. Данный вид отношений между объектами называется ассоциацией. Существует две разновидности ассоциации — композиция и агрегация.
На UML диаграммах ассоциация обозначается направленной стрелкой.
Композиция.
Композиция — это вид ассоциации, при котором один объект владеет (owns) другим объектом. При этом жизненный цикл зависимого объекта полностью контролируется объектом-владельцем. Зависимый объект не может существовать отдельно от своего владельца. Например, класс Car описывает автомобиль, который внутри себя содержит колеса — Wheels и двигатель — Engine.
Существование классов Wheels и Engine имеет смысл только в составе класса Car. Объекты этих классов создаются вместе с созданием основного объекта, в конструкторе. Если объект-владелец будет уничтожен, то также перестанут существовать и все его зависимые объекты.
Недостатком композиции можно считать то, что связь между объектами оказывается слишком жесткой.
На UML диаграммах композиция обозначается стрелкой с закрашенным ромбом, обозначающим строгую зависимость.
Агрегация.
Агрегация — другой вид ассоциации, при котором зависимый объект не принадлежит и не контролируется другим объектом и может существовать отдельно от него. В этом случае один объект просто использует (uses) другой объект.
Например, футбольная команда, представленная классом Team, имеет в своем составе набор игроков — список объектов класса Player. При этом каждый игрок может существовать и отдельно от команды (например, он может захотеть перейти в другую команду).
Наследование, композиция, агрегация
Нередко случается, что решив разобраться с какой-то новой темой, понятием, инструментом программирования, я читаю одну за другой статьи на различных сайтах в интернете. И, если тема сложная, то эти статьи могут не на шаг не приблизить меня к понимаю. И вдруг встречается статья, которая моментально дает озарение и все паззлы складываются воедино. Трудно определить, что отличает такую статью от других. Правильно подобранные слова, оптимальная логика изложения или же просто более релевантный пример. Я не претендую на то, что моя статься окажется новым словом в C# или же лучшей обучающей статьей. Но, возможно для кого-то она станет именно той, которая позволит разобраться, запомнить и начать правильно применять те понятия, о которых пойдет речь.
В объектно-ориентированных языках программирования существует три способа организации взаимодействия между классами. Наследование — это когда класс-наследник имеет все поля и методы родительского класса, и, как правило, добавляет какой-то новый функционал или/и поля. Наследование описывается словом «является». Легковой автомобиль является автомобилем. Вполне естественно, если он будет его наследником.
Ассоциация – это когда один класс включает в себя другой класс в качестве одного из полей. Ассоциация описывается словом «имеет». Автомобиль имеет двигатель. Вполне естественно, что он не будет являться наследником двигателя (хотя такая архитектура тоже возможна в некоторых ситуациях).
Выделяют два частных случая ассоциации: композицию и агрегацию.
Композиция – это когда двигатель не существует отдельно от автомобиля. Он создается при создании автомобиля и полностью управляется автомобилем. В типичном примере, экземпляр двигателя будет создаваться в конструкторе автомобиля.
Агрегация – это когда экземпляр двигателя создается где-то в другом месте кода, и передается в конструктор автомобиля в качестве параметра.
Хотя ведутся дискуссии о преимуществах того или иного способа организации взаимодействия между классами, какого-либо абстрактного правила не существует. Разработчик выбирает тот или иной путь основываясь на элементарной логике (“является” или “имеет”), но также принимает во внимание возможности и ограничения, которые дают и накладывают эти способы. Для того, чтобы увидеть эти возможности и ограничения, я попытался написать пример. Достаточно простой, чтобы код оставался компактным, но и достаточно развитый, чтобы в рамках одной программы можно было применить все три способа. И, главное, я попытался сделать этот пример как можно менее абстрактным – все объекты и экземпляры понятны и осязаемы.
Напишем простенькую игру – танковый бой. Играют два танка. Они поочередно стреляют и проигрывает тот, здоровье которого упало до нуля. В игре будут различные типы снарядов и брони. Для того, чтобы нанести урон необходимо во-первых, попасть по танку противника, во-вторых, пробить его броню. Если броня не пробита, урон не наносится. Логика игры построена на принципе «камень-ножницы-бумага»: то есть броня одного типа хорошо противостоит снарядам определенного типа, но плохо держит другие снаряды. Кроме того, снаряды, которые хорошо пробивают броню, наносят малый «заброневой» урон, и, напротив, наиболее «летальные» снаряды имеют меньше шансов пробить броню.
Создадим простенький класс для пушки. Он будет иметь два приватных поля: калибр и длину ствола. От калибра зависит урон, и, частично, способность к пробитию брони. От длины ствола – точность стрельбы.
Сделаем также конструктор для пушки:
Сделаем метод для получения калибра из других классов:
Помните, что для поражения цели должно произойти две вещи: попадание в цель и пробитие брони? Так вот, пушка будет отвечать за первую из них: попадание. Поэтому делаем булевый метод IsOnTarget, который принимает случайную величину (dice) и возвращает результат: попали или нет:
Целиком класс пушки выглядит следующим образом:
Здесь мы применили агрегацию. Где-то будет создана пушка. Потом к этой пушке будут создаваться снаряды, которые имеют указатель на пушку.
Теперь сделаем разные типы снарядов, которые будут наследовать абстрактный снаряд: фугасный, кумулятивный, подкалиберный. Фугасный наносит самый большой урон, кумулятивный – меньше, подкалиберный – еще меньше. Дочерние классы не имеют полей и вызывают конструктор базового снаряда, передавая ему пушку, и строковый тип. В дочернем классе переопределяется метод GetDamage() – вносятся коэффициенты, которые увеличат или уменьшат урон по сравнению с дефолтным.
Фугасный (дефолтный урон):
Кумулятивный (дефолтный урон х 0.6):
Подкалиберный (дефолтный урон х 0.3):
Обратите внимание, что в переопределенном методе GetDamage вызывается и метод базового класса. То есть, переопределив метод, мы также сохраняем возможность обратиться к дефолтному методу, использовав ключевое слово base).
Итак, для снарядов мы применили и агрегацию (пушка в базовом классе), и наследование.
Создадим теперь броню для танка. Здесь применим только наследование. Любая броня имеет толщину. Поэтому абстрактный класс брони будет иметь поле thickness, и строковое поле type, которое будет определятся при создании дочерних классов.
Броня будет в нашей игре определять пробита они или нет. Поэтому, у нее будет лишь один метод, который будет переопределяться в дочерних, в зависимости от типа брони.
Для того, чтобы конструктор танка остался более-менее компактным, сделаем два вспомогательных приватных метода, которые добавляют три типа брони соответствующей толщины, и наполняют боеукладку 10 снарядами каждого из трех типов:
Теперь конструктор танка выглядит вот таким образом:
Пользовательский интерфейс танка состоит из трех методов: выбрать броню, зарядить пушку, выстрелить.
Как я упомянул в начале, в этом примере я старался максимально уйти от абстрактных понятий, которые нужно все время держать в голове. Поэтому каждый экземпляр снаряда у нас равен физическому снаряду, который положили в боеукладку перед боем. Следовательно, снаряды могут закончится в самый неподходящий момент!
Этот интерфейс требует реализации метода Clone(). Вот она:
Теперь все супер реалистично: при выстреле генерируется dice, пушка рассчитывает попадание своим методом IsOnTarget, и, если попадание есть, то метод Shoot вернет экземпляр снаряда, а если промах – то вернет null.
Последний метод танка – его поведение при попадании вражеского снаряда:
Все готово. Остается только написать консольный (или неконсольный) вывод, в котором будет обеспечен пользовательский интерфейс и в цикле реализованы поочередные ходы игроков.
Подведем итоги. Мы написали программу, в которой использовали наследование, композицию и агрегацию, надеюсь, поняли и запомнили различия. Активно задействовали возможности полиморфизма, во-первых, когда любые экземпляры дочерних классов можно сложить в список, имеющий тип данных родительского, а во-вторых, создавая методы, которые принимают в качестве параметра родительский экземпляр, но внутри которых вызываются методы дочернего. По ходу текста я упоминал возможные альтернативные реализации – замену наследования на агрегацию, и, универсального рецепта тут нет. В нашей реализации наследование дало нам легкость добавления новых деталей в игру. Например, чтобы добавить новый тип снаряда нам нужно лишь:
Ниже – приведена диаграмма наших классов.
В финальном коде игры все «магические числа», которые использовались в тексте, вынесены в отдельный статический класс Config. К публичным полям статического класса мы можем обратиться из любого фрагмента нашего кода и его экземпляр не нужно (и невозможно) создавать. Вот так он выглядит:
Композиция, агрегация и ассоциация в Java
Изучите свойства и представление композиции, агрегации и ассоциации в Java.
1. введение
Объекты имеют отношения между собой, как в реальной жизни, так и в программировании. Иногда трудно понять или реализовать эти отношения.
В этом уроке мы сосредоточимся на подходе Java к трем иногда легко смешиваемым типам отношений: композиция, агрегация и ассоциация.
2. Состав
В качестве альтернативы, мы часто называем это отношением “имеет-а” (в отличие от отношения “есть-а”, которое является наследованием ).
Например, комната принадлежит зданию, или, другими словами, в здании есть комната. Таким образом, в основном, называем ли мы это “принадлежит” или “имеет”, это только вопрос точки зрения.
Композиция-это сильный вид отношений “имеет-а”, потому что содержащий объект владеет им. Таким образом, жизненные циклы объектов связаны. Это означает, что если мы уничтожим объект-владельца, его члены также будут уничтожены вместе с ним. Например, комната разрушается вместе со зданием в нашем предыдущем примере.
Обратите внимание, что это не означает, что содержащий объект не может существовать без какой-либо из его частей. Например, мы можем снести все стены внутри здания, следовательно, уничтожить комнаты. Но здание все равно будет существовать.
2.1. UML
В UML мы обозначаем композицию следующим символом:
Обратите внимание, что алмаз находится в содержащем его объекте и является основанием линии, а не наконечником стрелы. Для ясности мы тоже часто рисуем наконечник стрелы:
Итак, мы можем использовать эту конструкцию UML для нашего примера здания:
2.2. Исходный код
В Java мы можем смоделировать это с помощью нестатического внутреннего класса:
В качестве альтернативы мы также можем объявить этот класс в теле метода. Не имеет значения, является ли это именованный класс, анонимный класс или лямбда:
Обратите внимание, что очень важно, чтобы наш внутренний класс был нестатическим, поскольку он связывает все свои экземпляры с содержащим классом.
Обычно содержащий объект хочет получить доступ к своим членам. Поэтому мы должны хранить их ссылки:
Обратите внимание, что все внутренние объекты класса хранят неявную ссылку на содержащийся в них объект. В результате нам не нужно хранить его вручную, чтобы получить к нему доступ:
3. Агрегация
Агрегация также является отношением “есть-есть”. Что отличает его от композиции, так это то, что он не предполагает владения. В результате жизненный цикл объектов не привязан: каждый из них может существовать независимо друг от друга.
Например, автомобиль и его колеса. Мы можем снять колеса, и они все еще будут существовать. Мы можем установить другие (уже существующие) колеса или установить их на другой автомобиль, и все будет работать просто отлично.
3.1. UML
Агрегация очень похожа на композицию. Единственное логическое различие заключается в том, что агрегация-это более слабая связь.
Поэтому представления UML также очень похожи. Разница лишь в том, что алмаз пуст:
Тогда для автомобилей и колес мы бы сделали:
3.2. Исходный код
В Java мы можем моделировать агрегацию с помощью простой старой ссылки:
Членом может быть любой тип класса, кроме нестатического внутреннего класса.
В приведенном выше фрагменте кода оба класса имеют свой отдельный исходный файл. Однако мы также можем использовать статический внутренний класс:
Обратите внимание, что Java создаст неявную ссылку только в нестатических внутренних классах. Из-за этого мы должны поддерживать отношения вручную там, где нам это нужно:
4. Ассоциация
Ассоциация означает только то, что объекты “знают” друг друга. Например, мать и ее ребенок.
4.1. UML
В UML мы можем отметить ассоциацию стрелкой:
Если связь двунаправленная, мы можем использовать две стрелки, стрелку с наконечником на обоих концах или линию без наконечников:
Мы можем представить мать и ее ребенка в UML, тогда:
4.2. Исходный код
В Java мы можем моделировать ассоциацию так же, как и агрегацию:
Но подождите, как мы можем определить, означает ли ссылка агрегацию или ассоциацию?
Ну, мы не можем. Разница только логична: является ли один из объектов частью другого или нет.
Кроме того, мы должны поддерживать ссылки вручную на обоих концах, как мы делали с агрегацией:
5. UML Sidenote
Для ясности иногда мы хотим определить мощность отношения на диаграмме UML. Мы можем сделать это, записав его на концах стрелки:
Обратите внимание, что нет смысла писать ноль в качестве мощности, потому что это означает, что нет никакой связи. Единственное исключение-это когда мы хотим использовать диапазон для указания необязательной связи:
Также обратите внимание, что, поскольку в составе есть только один владелец, мы не указываем его на диаграммах.
6. Сложный Пример
Давайте рассмотрим (немного) более сложный пример!
Мы смоделируем университет, в котором есть свои кафедры. На каждой кафедре работают профессора, у которых также есть друзья друг среди друга.
Будут ли кафедры существовать после того, как мы закроем университет? Конечно, нет, поэтому это композиция.
Но профессора все равно будут существовать (надеюсь). Мы должны решить, что более логично: считать ли профессоров частью кафедр или нет. Альтернативно: являются ли они членами департаментов или нет? Да, это так. Следовательно, это агрегация. Кроме того, профессор может работать на нескольких кафедрах.
Отношения между профессорами являются ассоциативными, потому что нет никакого смысла говорить, что профессор является частью другого.
В результате мы можем смоделировать этот пример со следующей диаграммой UML:
И код Java выглядит следующим образом:
7. Заключение
В этой статье мы рассмотрели свойства и представление композиции, агрегации и ассоциации. Мы также видели, как моделировать эти отношения в UML и Java.