Урок №148. Агрегация

  Юрий  | 

  |

  Обновл. 13 Сен 2021  | 

 58863

 ǀ   15 

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

На этом уроке мы рассмотрим второй подтип композиции объекта — агрегацию.

Агрегация

Для реализации агрегации целое и его части должны соответствовать следующим отношениям:

   Часть (член) является частью целого (класса).

   Часть (член) может принадлежать более чем одному целому (классу) в моменте.

   Часть (член) существует, не управляемая целым (классом).

   Часть (член) не знает о существовании целого (класса).

Как и в случае с подтипом композиция, отношения в агрегации также являются отношениями части-целого и однонаправленные. Однако, в отличие от композиции, части могут принадлежать более чем одному целому в моменте, и целое не управляет существованием и продолжительностью жизни частей. При создании/уничтожении агрегации, целое не несет ответственности за создание/уничтожение своих частей.

Например, рассмотрим отношения между человеком и его домашним адресом. У каждого человека есть свой адрес. Однако этот адрес может принадлежать более чем одному человеку в моменте, например, вам и вашему соседу по комнате или родственникам, которые живут вместе с вами. К тому же этот адрес не управляется человеком — адрес существовал до того, как человек заселился и будет существовать после того, как человек выселится. Кроме того, человек знает, по какому адресу он живет, но адрес, в свою очередь, не знает, что это за человек и вообще, сколько их там находится. Такие отношения и являются агрегацией.

В качестве альтернативы рассмотрим автомобиль и двигатель. Двигатель является частью автомобиля. И хотя двигатель принадлежит автомобилю, он может принадлежать и другим объектам, например, человеку, которому принадлежит автомобиль. Автомобиль не несет ответственности за создание или уничтожение двигателя. И в то же время автомобиль знает, что у него есть двигатель (ведь благодаря ему он двигается), но сам двигатель не знает, что он является частью автомобиля.

Когда дело доходит до моделирования физических объектов, использование термина «уничтожение» может быть немного расплывчатым. Возникает вопрос: «Если бы метеорит упал с неба и раздавил машину, то можно ли считать, что и все части машины также были бы уничтожены?». Да, конечно. Но это вина метеорита, а не автомобиля. Важным моментом является то, что автомобиль не несет ответственности за уничтожение своих частей (но есть и внешняя сила, которая может этому поспособствовать).

Мы можем сказать, что типом отношений в агрегации является «имеет» (отдел «имеет» работников, автомобиль «имеет» двигатель).

Подобно композиции, части агрегации могут быть сингулярными или мультипликативными.

Реализация агрегации


Поскольку агрегация подобна композиции, так как обе состоят из отношений части-целого, то они реализуются почти одинаково, а разница между ними в основном семантическая. В композиции мы добавляем части к целому, используя обычные переменные-члены (или указатели, когда в классе происходит динамическое выделение/освобождение памяти).

В агрегации мы также добавляем части к целому, используя переменные-члены. Однако этими переменными-членами обычно являются либо ссылки, либо указатели, которые указывают на объекты, созданные за пределами класса. Следовательно, агрегация принимает части, на которые она будет указывать, в качестве параметров конструктора или, если параметров нет, части добавляются позже через функции доступа или через перегруженные операторы.

Поскольку эти части существуют вне области видимости класса, то при уничтожении класса, переменные-члены в виде ссылок или указателей также уничтожаются (но не удаляются значения, на которые они указывают). Следовательно, сами части продолжают существовать дальше.

Рассмотрим пример Работника и Отдела детально. Чтобы было проще, в Отделе работает только один Работник и он не знает, Работником какого именно Отдела он является:

Здесь Работник создается независимо от Отдела, а затем переходит в параметр конструктора класса Отдела. Когда department уничтожается, то указатель m_worker уничтожается также, но сам Работник то не удаляется, он продолжает существовать до тех пор, пока не будет уничтожен в функции main().

Выбирайте правильные отношения

Хотя в вышеприведенном примере может показаться немного глупым, что Работник не знает, в каком Отделе он работает, но это совершенно нормально в контексте данной программы. Когда вы определяете тип отношений для реализации, внедряйте самые простые и понятные отношения, которые соответствуют вашим потребностям, а не те, которые, как вам кажется, подойдут лучше всего, но они сложнее и навороченнее.

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

Правило: Реализовывайте самые простые отношения, которые соответствуют потребностям вашей программы, а не то, что, как вам кажется, будет лучше.

Композиция и агрегация


В композиции:

   Используются обычные переменные-члены.

   Используются указатели, если класс реализовывает собственное управление памятью (происходит динамическое выделение/освобождение памяти).

   Класс ответственный за создание/уничтожение своих частей.

В агрегации:

   Используются указатели/ссылки, которые указывают/ссылаются на части вне класса.

   Класс не несет ответственности за создание/уничтожение своих частей.

Стоит отметить, что идеи композиции и агрегации не являются взаимоисключающими и могут свободно использоваться в одном классе. Вполне возможно реализовать класс, который отвечает за создание/уничтожение только определенных частей. Например, наш класс Department мог бы иметь и Имя, и Работника. Имя было бы добавлено в класс через композицию и создавалось/уничтожалось бы вместе с объектами класса Department. А Работник был бы добавлен в Department через агрегацию и создавался/уничтожался бы независимо/отдельно.

Хотя агрегации могут быть чрезвычайно полезными, они также потенциально опасны. Поскольку в агрегации автоматически не осуществляется освобождение памяти, которую могут занимать части, то это должны контролировать вы сами. Если вы забыли выполнить очистку (освободить память), думая, что это должен сделать класс, то тогда произойдет утечка памяти, поэтому следует быть осторожным.

Примечание: По разным историческим и другим причинам, в отличие от композиции, определение агрегации не является единственно правильным — вы можете увидеть, что другие ресурсы/сайты/учебники определяют агрегацию несколько иначе, нежели изложено здесь. Это нормально, просто знайте об этом.

Тест

Задание №1

Что бы вы использовали (агрегацию или композицию) для создания следующих объектов? Список создаваемых объектов:

   Красный шар.

   Работодатель, который нанимает людей.

   Факультет в университете.

   Ваш возраст.

   Мешок с шариками.

Ответ №1

   Композиция: Цвет является неотъемлемым свойством шара.

   Агрегация: Работодатель в начале не имеет никаких работников и, надеемся, не уничтожит всех своих сотрудников, когда обанкротится.

   Композиция: Факультеты не могут существовать отдельно от университета.

   Композиция: Ваш возраст является неотъемлемым Вашим свойством.

   Агрегация: Мешок и шарики внутри являются независимыми объектами и могут существовать отдельно.

Задание №2

Обновите вышеприведенный пример с Работником/Отделом так, чтобы Отдел мог состоять из нескольких Работников. Следующий код:

Должен выдавать следующий результат:

Department: Anton Ivan Max
Anton still exists!
Ivan still exists!
Max still exists!

Подсказки:

   Используйте std::vector для хранения Работников.

   Используйте std::vector::push_back() для добавления Работника.

   Используйте std::vector::size() для получения длины std::vector.

Ответ №2


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (181 оценок, среднее: 4,88 из 5)
Загрузка...

Комментариев: 15

  1. Влад:

    Подскажите, пожалуйста, зачем тут динамическое выделение памяти для работников? Если можно создать объект со статическим временем жизни и не освобождать память его в конце?

    1. Powarito:

      Ну тут простые примеры на сайте, чтобы никто не сидел долго и не тратил много времени на их понимание.

      В реальности же часто бывают случаи, когда нужно, чтобы объекты обязательно были созданы динамически.

  2. Anton:

    "Агрегация: Работодатель в начале не имеет никаких работников и, надеемся, не уничтожит всех своих сотрудников, когда обанкротится."
    Здесь все таки композиция и вот почему.
    Сотрудник — это абстракция. Т.е. сотрудник — это не просто человек, а также и набор обязанностей с договором.

    Создать сотрудника — нанять его — начало времени жизни сотрудника.

    Убить сотрудника все таки можно — уволить — конец времени жизни сотрудника. Сам человек-то от этого не погибает, а сотрудник — да.

    Временем жизни сотрудника управляет работодатель и трудовой кодекс.

    Т.е. выбор отношений между объектами зависит от выбранной модели. Все зависит от глубины познаний предметной области.

    1. Владимир:

      Как и просил автор "применяйте самые простые отношения а не те, которые вам кажется что лучше".
      Помню когда на работе начальник собрал нас и сказал что умер сотрудник из "такого то" отдела, ни у кого что то не появилось мысли что его уволили.
      Это получается когда человек говорит я был сотрудником "Такой то" компании, это можно понимать что он говорит что он был живым? Сумбур какой то получается.

  3. Евгений:

    "Правило: Реализовывайте самые простые отношения, которые соответствует потребностям вашей программы, а не то, что, как вам кажется, будет лучшим." тут не стоит забывать о том, что в будущем, возможно, потребуется расширить возможности программы, поэтому архитектурно нужно продумывать множество аспектов, и может так получиться, что классы будут сложными, но в будущем это скажется положительно.

  4. Константин:

    Написал так, чтобы в массиве отдела хранились указатели на работников:

  5. Victor:

    Ну тут относительно всё, большинство можно описать и агрегацией, и композицией. В зависимости от задачи… И вычислительных особенностей. Мне кажется не так надо передавать свойства реальных обьектов как просто подобрать оптимальный вариант к задаче, с т.з производительности, памяти, и интерфейса взаимодействия. Так ведь?

    1. Victor:

      И ещё. Если например у класса который реализует агрегацию, есть методы которые управляют жизнью внешнего обьекта?
      Ну например у Car
      есть метод destroyEngine(Engine e);
      Который уничтожает внешний обьект, это будет композиция уже?( ну если он будет вызываться в деструкторе, то точно )..
      В предыдущем уроке ведь указано что может в композиции создаваться внутренний обьект вне обьекта родителя. И насколько это вообще адекватно?
      А если destroy метод вызывается только пользователем? Это уже агрегация?
      И в первом случае, когда обьект управляет временем жизни дочернего обьекта, как предупредить пользователя об этом? средствами яп. Допутим он создал энжин, передал его в кар, кар отработал, умер, и разрушил энжин, пользователь об этом не знает, и вызывает engine->upgrade(…); как избежать таких случаев??? Или хотя бы предупредить о не возможности таких случаев, того кто будет использовать Car.

  6. somebox:

    Не могу до конца понять, обязательно ли каждый раз создавать функцию перегрузки оператора вывода?

    1. Анастасия:

      Если есть потребность выводить данные класса по имени объекта — да

  7. Nikita:

    А как понять, что и когда использовать, агрегацию по ссылке или по указателю?

    1. Александр:

      ссылки безопасней, но менее гибкие… отсюда и выбор

      Если нужна большая гибкость и один и тот же указатель в процессе работы с классом может изменяться, то выбираем указатель. Если нет — ссылку

      Продолжая аналогии из статьи:
      Если в автомобиле возможна полная замена двигателя на другой (или вообще временное нахождение авто без двигателя) — указатель
      Если двигатель можно извлечь, отремонтировать и поставить назад, но нельзя заменить на другой или полностью "отвязать" от авто — ссылка

  8. Игорь:

    Всем привет.Моё решение:

  9. Anastasia:

    Для вывода вектора необязательно использовать size:

    1. Анатолий:

      Тут так же можно просто разименовать указать чтобы вывести имена:

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *