Композиция (программирование)
Агрегирование в общем смысле — это объединение нескольких элементов в единое целое. Результат агрегирования называют агрегатом.
В программировании под агрегированием (так же называемым композицией и включением) подразумевают методику создания нового класса из уже существующих классов. Об агрегировании также часто говорят как об «отношении принадлежности» по принципу «у машины есть корпус, колеса и двигатель».
Вложенные объекты нового класса обычно объявляются закрытыми, что делает их недоступными для прикладных программистов, работающих с классом. С другой стороны, создатель класса может изменять эти объекты, не нарушая работы существующего клиентского кода. Кроме того, замена вложенных объектов на стадии выполнения программы позволяет динамически изменять её поведение. Механизм наследования такой гибкостью не обладает, поскольку для производных классов устанавливаются ограничения, проверяемые на стадии компиляции.
На базе агрегирования реализуется методика делегирования, когда поставленная перед внешним объектом задача перепоручается внутреннему объекту, специализирующемуся на решении задач такого рода.
Wikimedia Foundation . 2010 .
- Композиция «Золотое руно»
- Композиция (музыка)
Смотреть что такое «Композиция (программирование)» в других словарях:
- Агрегирование (программирование) — В этой статье не хватает ссылок на источники информации. Информация должна быть проверяема, иначе она может быть поставлена под сомнение и удалена. Вы можете отредактировать эту статью, добавив ссылки на авторитетные источники … Википедия
- Субъектно-ориентированное программирование — Парадигмы программирования Агентно ориентированная Компонентно ориентированная Конкатенативная Декларативная (контрастирует с Императивной) Ограничениями Функциональная Потоком данных Таблично ориентированная (электронные таблицы) Реактивная … Википедия
- Класс (программирование) — У этого термина существуют и другие значения, см. Класс. Класс в программировании набор методов и функций. Другие абстрактные типы данных метаклассы, интерфейсы, структуры, перечисления характеризуются какими то своими, другими… … Википедия
- Класс (объектно-ориентированное программирование) — Класс, наряду с понятием «объект», является важным понятием объектно ориентированного подхода в программировании (хотя существуют и бесклассовые объектно ориентированные языки, например, Прототипное программирование). Под классом подразумевается… … Википедия
- Функциональная зависимость (программирование) — Функциональная зависимость концепция, лежащая в основе многих вопросов, связанных с реляционными базами данных, включая, в частности, их проектирование. Математически представляет бинарное отношение между множествами атрибутов данного… … Википедия
- That’s the Way Love Goes — «That’s the Way Love Goes» Сингл Джанет Джексон из альбома janet … Википедия
- New Order — Об альбоме группы Testament см. статью The New Order. New Order New … Википедия
- Диаграмма классов — Для улучшения этой статьи желательно?: Викифицировать статью. В UML диаграмма классов является типом диаграммы статичес … Википедия
- Vision of Love — «Vision of Love» Сингл Мэрайи Кэри из альбома Mariah Carey Выпущен 15 мая 1990 Формат CD сингл Кассетный сингл 7 сингл … Википедия
- Music (альбом Мадонны) — У этого термина существуют и другие значения, см. Music. Music … Википедия
Агрегация и композиция
Здравствуйте! Всегда считал, что агрегация — это синоним композиции, однако наткнулся на блог в интернете, где приводятся отличия композиции от агрегации. Мне это снесло крышу. Поясните, пожалуйста, плюсы/минусы и того и другого на небольших примерах. Как это влияет на расширяемость, тестируемость и т. д.
Отслеживать
11.5k 8 8 золотых знаков 42 42 серебряных знака 69 69 бронзовых знаков
задан 28 ноя 2016 в 6:36
2,622 1 1 золотой знак 17 17 серебряных знаков 30 30 бронзовых знаков
Там пояснено достаточно подробно. Вы лучше конкретные вопросы напишите.
28 ноя 2016 в 10:56
да особо нечего спросить так сходу. хорошо бы если бы кто-нибудь разъяснил плюсы/минусы и того и другого на небольших примерах. как это влияет на расширяемость, тестируемость и т.д.
28 ноя 2016 в 11:45
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Существует несколько видов взаимодействия объектов, объединенных под общим понятием «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. Если очень хочется использовать пример из реального мира, то для объяснения композиции и агрегации может подойти . отвертка. Если отвертка цельная, т.е. ручка и насадка намертво связаны друг с другом, то мы имеем отношение композиции. Если же насадка съемная и может существовать без ручки или же использоваться с другой ручкой, то мы имеем отношение агрегации.
Композиция в Python
Еще одной особенностью объектно-ориентированного программирования является возможность реализовывать так называемый композиционный подход. Заключается он в том, что есть класс-контейнер, он же агрегатор, который включает в себя вызовы других классов. В результате получается, что при создании объекта класса-контейнера, также создаются объекты других классов.
Чтобы понять, зачем нужна композиция в программировании, проведем аналогию с реальным миром. Большинство биологических и технических объектов состоят из более простых частей, также являющихся объектами. Например, животное состоит из различный органов (сердце, желудок), компьютер — из различного «железа» (процессор, память).
Не следует путать композицию с наследованием, в том числе множественным. Наследование предполагает принадлежность к какой-то общности (похожесть), а композиция — формирование целого из частей. Наследуются атрибуты, т. е. возможности, другого класса, при этом объектов непосредственно родительского класса не создается. При композиции же класс-агрегатор создает объекты других классов.
Рассмотрим на примере реализацию композиции в Python. Пусть, требуется написать программу, которая вычисляет площадь обоев для оклеивания помещения. При этом окна, двери, пол и потолок оклеивать не надо.
Прежде, чем писать программу, займемся объектно-ориентированным проектированием. То есть разберемся, что к чему. Комната – это прямоугольный параллелепипед, состоящий из шести прямоугольников. Его площадь представляет собой сумму площадей составляющих его прямоугольников. Площадь прямоугольника равна произведению его длины на ширину.
По условию задачи обои клеятся только на стены, следовательно площади верхнего и нижнего прямоугольников нам не нужны. Из рисунка видно, что площадь одной стены равна xz , второй – уz . Противоположные прямоугольники равны, значит общая площадь четырех прямоугольников равна S = 2xz + 2уz = 2z(x+y) . Потом из этой площади надо будет вычесть общую площадь дверей и окон, поскольку они не оклеиваются.
Можно выделить три типа объектов – окна, двери и комнаты. Получается три класса. Окна и двери являются частями комнаты, поэтому пусть они входят в состав объекта-помещения.
Для данной задачи существенное значение имеют только два свойства – длина и ширина. Поэтому классы «окна» и «двери» можно объединить в один. Если бы были важны другие свойства (например, толщина стекла, материал двери), то следовало бы для окон создать один класс, а для дверей – другой. Пока обойдемся одним, и все что нам нужно от него – площадь объекта:
class WinDoor: def __init__(self, x, y): self.square = x * y
Класс «комната» – это класс-контейнер для окон и дверей. Он должен содержать вызовы класса «ОкноДверь».
Хотя помещение не может быть совсем без окон и дверей, но может быть чуланом, дверь которого также оклеивается обоями. Поэтому имеет смысл в конструктор класса вынести только размеры самого помещения, без учета элементов «дизайна», а последние добавлять вызовом специально предназначенного для этого метода, который будет добавлять объекты-компоненты в список.
class Room: def __init__(self, x, y, z): self.square = 2 * z * (x + y) self.wd = [] def add_wd(self, w, h): self.wd.append(WinDoor(w, h)) def work_surface(self): new_square = self.square for i in self.wd: new_square -= i.square return new_square r1 = Room(6, 3, 2.7) print(r1.square) # выведет 48.6 r1.add_wd(1, 1) r1.add_wd(1, 1) r1.add_wd(1, 2) print(r1.work_surface()) # выведет 44.6
Практическая работа
Приведенная выше программа имеет ряд недочетов и недоработок. Требуется исправить и доработать, согласно следующему плану.
При вычислении оклеиваемой поверхности мы не «портим» поле self.square . В нем так и остается полная площадь стен. Ведь она может понадобиться, если в списке wd произойдут изменения, и придется заново вычислять оклеиваемую площадь.
Однако в классе не предусмотрено сохранение длин сторон, хотя они тоже могут понадобиться. Например, если потребуется изменить одну из величин у уже существующего объекта. Площадь же помещения всегда можно вычислить, если хранить исходные параметры. Поэтому сохранять саму площадь в поле не обязательно.
Исправьте код так, чтобы у объектов Room было четыре поля – width , length , height и wd . Площади (полная и оклеиваемая) должны вычислять лишь при необходимости путем вызова методов.
Программа вычисляет площадь под оклейку, но ничего не говорит о том, сколько потребуется рулонов обоев. Добавьте метод, который принимает в качестве аргументов длину и ширину одного рулона, а возвращает количество необходимых, исходя из оклеиваемой площади.
Разработайте интерфейс программы. Пусть она запрашивает у пользователя данные и выдает ему площадь оклеиваемой поверхности и количество необходимых рулонов.
Курс с примерами решений практических работ:
pdf-версия
X Скрыть Наверх
Объектно-ориентированное программирование на Python
Что такое композиция в программировании
Наследование было одной из самых популярных характеристик ООП с момента его появления. Но в наши дни оно, похоже, уже не рекомендуется как хорошая практика в программировании. Легко найти множество дискуссий и статей на тему «Композиция вместо наследования» в качестве меры предосторожности для программистов. Некоторые современные языки программирования, такие как Go, вообще не позволяют использовать наследование, а только альтернативу — композицию.
Наследование
Когда дочерний класс наследует от родительского класса, дочерний класс приобретает все поведенческие характеристики родительского класса. Наследование создает иерархию классов — вы можете представить ее как дерево классов.
Композиция
Композиция, в отличие от наследования, позволяет создавать сложные типы путем комбинирования объектов (компонентов) других типов, а не наследования от базового или родительского класса. Проще говоря, композиция содержит экземпляры других классов, которые реализуют желаемую функциональность.
Мы можем представить композицию как игру с конструктором Лего, а компоненты — это кирпичики Лего.
Наследование и композиция
Основное различие между наследованием и композицией заключается в отношениях между объектами.
Наследование: «является». Например, автомобиль — это транспортное средство.
Композиция: «имеет». Например, у автомобиля есть руль.
Наследование известно как самая тесная форма связи в объектно-ориентированном программировании. Изменение базового класса может вызвать нежелательные побочные эффекты в его подклассах или даже во всей кодовой базе.
Композиция — это гораздо более слабая связь. В сочетании с внедрением зависимости (Dependency injection, DI) она обеспечивает большую гибкость, а также позволяет нам изменять поведение во время выполнения программы.
При создании класса, объединяющего различные компоненты, естественнее использовать композицию, чем пытаться найти общность между ними и создавать дерево классов.
Такой подход позволяет легче приспособиться к будущим изменениям требований, которые могут потребовать полной перестройки дерева классов в подходе наследования. Мы можем просто добавить новый компонент в объединенный класс, а не модифицировать суперкласс для адаптации изменений.
Они также отличаются по назначению.
Наследование: Конструирование класса на основе того, чем он является.
Композиция: Создание класса на основе того, что он делает.
В большинстве случаев композиция может использоваться как взаимозаменяемое понятие с наследованием. Одна вещь, которая делает наследование таким известным, — это полиморфизм. Композиция изначально не рассчитана на полиморфизм. Но большинство языков программирования позволяют нам делать это с помощью интерфейсов.
Проблемы использования наследования
Вы быстро получите довольно глубокое и широкое наследование. Иногда вы можете оказаться с глубокой иерархией классов. Одно изменение в верхнем классе может уничтожить все нижележащие классы. В тот момент, когда вы решили наследоваться от другого класса, вы привязываете свой класс к нему. Вы никогда не уверены в том, что собирается делать суперкласс, поскольку это вне вашего контроля. Это известно как проблема хрупкого базового класса.
Иерархию классов очень трудно изменить после ее развертывания. Одним из вариантов было бы переписать все заново.
Иногда мы действительно используем наследование не по назначению. Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP) является наиболее важным руководством для определения того, подходит ли наследование для вашего проекта или нет.
Например: B наследуется от A. Используйте подстановку Лисков, чтобы обосновать связь между B и A, затем измените связь и снова обоснуйте ее. Если отношения выглядят логичными в обоих направлениях, лучше не использовать наследование.
При наследовании вы можете рассмотреть, что делает суперкласс, а также то, что ваши подклассы могут захотеть отменить/изменить. Вместо того чтобы сосредоточиться на своей области применения, вам, возможно, придется потратить время на понимание большого количества кода во всем вашем приложении, чтобы просто «заставить» его работать хорошо.
Одна из проблем с наследованием заключается в том, что оно побуждает вас предсказывать будущее, а это ловушка. Наследование побуждает вас создавать таксономию объектов на самом раннем этапе проекта. В результате вы, вероятно, допустите ошибки в проектировании. В некоторых случаях оно также нарушает инкапсуляцию, открывая свойства/методы для использования подклассами.
Еще одним недостатком наследования является то, что оно значительно затрудняет отладку, когда иерархия классов становится слишком сложной. Простая трассировка стека может вылиться за пределы экрана при наличии 30 уровней наследования.
Заключение
У всего есть свои плюсы и минусы. Как и во всем, что касается разработки программного обеспечения, здесь необходимо искать компромиссы. В большинстве случаев композиция может заменить наследование. Но это не серебряная пуля.
Выбор инструмента программирования подобен выбору правильного кухонного инструмента: вы не будете использовать нож для масла, чтобы нарезать овощи, и точно так же не стоит выбирать композицию для каждого сценария программирования.
Используйте наследование, когда оно действительно необходимо, а не просто для повторного использования кода. Когда вы думаете об использовании наследования, спросите себя, действительно ли подкласс является более специализированной версией суперкласса, иначе в один прекрасный день вы столкнетесь с неразберихой.
Когда речь заходит о наследовании, всегда спрашивайте себя еще раз: «Не имеет ли большего смысла использовать композицию?».