Здравствуйте!
Всегда считал, что агрегация — это синоним композиции, однако наткнулся на блог в интернете, где приводятся отличия композиции от агрегации
Мне это снесло крышу. Поясните, пожалуйста, плюсы/минусы и того и другого на небольших примерах. Как это влияет на расширяемость, тестируемость и т. д.
Ответ
Существует несколько видов взаимодействия объектов, объединенных под общим понятием "Has-A Relationship" или "Part Of Relationship". Это отношение означает, что один объект является составной частью другого объекта.
Существует два подвида этого отношения: если один объект создает другой объект и время жизни "части" зависит от времени жизни целого, то это называется "композиция", если же один объект получает ссылку (указатель) на другой объект в процессе конструирования, то это уже агрегация.
Давайте рассмотрим пример из .NET Framework, чтобы увидеть, какие ограничения/последствия несут эти отношения: StringWriter + StringBuilder
Класс StringWriter - это специализированная версия класса TextWriter, которые активно используются при сериализации и для получения текстового представления объектов.
Конкретно StringWriter создает строковое представление объекта или графа объектов и опирается на экземпляр StringBuilder в своей работе. Т.е. мы можем сказать, что 'StringWriter HAS A StringBuilder' или 'StringBuilder is part of StringWriter'. Теперь давайте посмотрим, как решить, должен ли StringWriter получать экземпляр StringBuilder-а извне или создавать его?
С одной стороны, нам, как клиенту класса StringWriter часто бывает все равно, что именно используется внутри этого класса для получения строкового представления. Это значит, что с точки зрения простоты использования лучше, чтобы StringWriter создавал экземпляр StringBuilder-а самостоятельно.
Но, с другой стороны, конкретный объект StringWriter-а может отвечать лишь за получения части строкового представления, а другая часть строки может вычисляться другим способом. С этой точки зрения, лучше, чтобы StringWriter принимал экземлпяр StringBuilder-а в конструкторе. Это же справедливо и для высоконагруженных систем, в которых разумно использование пула объектов.
Поскольку StringWriter - это библиотечный класс, который должен поддерживать оба сценария, то у него есть перегруженные версии конструктора: один из них создает StringBuilder внутри, а другой - принимает его снаружи.
Другими словами, выбор между композицией и агрегацией основан на соблюдении балланса между различными требованиями в дизайне:
Композиция: объект A управляет временем жизни объекта B
Плюсы:
Композиция позволяет скрыть отношение использования объектов от глаз клиента.
Делает API использования класса более простым и позволяет перейти от использования одного класса, к другому (например, StringWriter мог бы поменять реализацию и начать использовать другой тип, например CustomStringBuilder).
Минусы:
Отношение достаточно жесткое, поскольку один объект должен уметь создавать другой: он должен знать конкретный тип и иметь доступ к функции создания. Композиция не позволяет использовать интерфейсы (без привлечения фабрик) и требует, чтобы класс имел доступ к конструктору другого класса: представьте, что конструктор StringBuilder-а является внутренним или же это интерфейс IStringBuilder и только клиенский код знает, какой экземпляр должен использоваться здесь и сейчас.
Агрегация: объект А получает ссылку на объект B
Плюсы:
Более слабая связанность между объектом и его клиентом. Теперь мы можем использовать интерфейсы и одному объекту не нужно знать, как именно создавать другой объект.
Большая гибкость. Вытекает из первого пункта
Минусы:
Выставление наружу деталей реализации. Поскольку клиент класса должен предоставить зависимость в момент создания объекта (передать экземпляр StringBuilder-а в момент создания StringWriter-а, то сам факт этого отношения становится известен клиенту.
Из первого пункта вытекает увеличение сложности в работе клиентов, а также большая "жесткость" решения в долгосрочной перспективе. Теперь автор класса TextWriter уже не может принять решение самостоятельно и перейти от StringBuilder-а к чему-то другому. Да, можно "добавить еще один уровень абстракции" и выделить интерфейс IStringBuilder, но разорвать это отношение совсем будет невозможно без поломки всех существующих клиентов.
В заключении: дизайн - это поиск компромисса между различными факторами. Композиция проще с точки зрения клиентов класса, но налагает определенные ограничения: "целое" должно уметь создавать "составную часть". Агрегация гибче, но налагает другие ограничения: теперь "целое" не скрывает о существовании "составной части", а значит и не сможет заменить ее на другую "составную часть" в будущем.
P.S. Если очень хочется использовать пример из реального мира, то для объяснения композиции и агрегации может подойти ... отвертка. Если отвертка цельная, т.е. ручка и насадка намертво связаны друг с другом, то мы имеем отношение композиции. Если же насадка съемная и может существовать без ручки или же использоваться с другой ручкой, то мы имеем отношение агрегации
Комментариев нет:
Отправить комментарий