Перейти к основному содержимому

Композиция, агрегация и ассоциация в Java

· 6 мин. чтения

1. Введение

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

В этом уроке мы сосредоточимся на подходе Java к трем типам отношений, которые иногда легко перепутать: композиция, агрегация и ассоциация.

2. Состав

Композиция — это тип отношений «принадлежит». Это означает, что один из объектов является логически большей структурой, содержащей другой объект. Другими словами, это часть или член другого объекта.

В качестве альтернативы мы часто называем это отношением «имеет-а» (в отличие от отношения «есть-а», которое является наследованием ).

Например, комната принадлежит зданию, или, другими словами, в здании есть комната. Таким образом, в основном, называем ли мы это «принадлежит» или «имеет», это только вопрос точки зрения.

Композиция — это сильный вид отношения «имеет», потому что содержащий объект владеет ею. Следовательно, жизненные циклы объектов связаны. Это означает, что если мы уничтожим объект-владелец, его члены также будут уничтожены вместе с ним. Например, комната уничтожена вместе со зданием в нашем предыдущем примере.

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

С точки зрения кардинальности, содержащий объект может иметь столько частей, сколько мы хотим. Однако все части должны иметь ровно один контейнер .

2.1. UML

В UML мы обозначаем композицию следующим символом:

./e7384589157fc6e28c706876b9cd93e3.png

Обратите внимание, что ромб находится на содержащем объекте и является основанием линии, а не стрелкой. Для ясности мы часто рисуем и наконечник стрелки:

./e3c412da1bafe7c425651381bf58b2a3.png

Итак, мы можем использовать эту конструкцию UML для нашего примера Building-Room:

./b4e0905bcac6b3721c73ac9860347690.png

2.2. Исходный код

В Java мы можем смоделировать это с помощью нестатического внутреннего класса:

class Building {
class Room {}
}

В качестве альтернативы мы также можем объявить этот класс в теле метода. Неважно, именованный это класс, анонимный класс или лямбда:

class Building {
Room createAnonymousRoom() {
return new Room() {
@Override
void doInRoom() {}
};
}

Room createInlineRoom() {
class InlineRoom implements Room {
@Override
void doInRoom() {}
}
return new InlineRoom();
}

Room createLambdaRoom() {
return () -> {};
}

interface Room {
void doInRoom();
}
}

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

Обычно содержащий объект хочет получить доступ к своим членам. Поэтому мы должны хранить их ссылки:

class Building {
List<Room> rooms;
class Room {}
}

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

class Building {
String address;

class Room {
String getBuildingAddress() {
return Building.this.address;
}
}
}

3. Агрегация

Агрегация также является отношением «имеет-а». Что отличает его от композиции, так это то, что он не предполагает владения. В результате жизненные циклы объектов не связаны: каждый из них может существовать независимо друг от друга.

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

Конечно, автомобиль без колес или с оторванным колесом не будет так полезен, как автомобиль с колесами. Но именно поэтому изначально существовали эти отношения: чтобы собрать части в более крупную конструкцию, которая способна на большее, чем ее части .

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

3.1. UML

Агрегация очень похожа на композицию. Единственное логическое отличие состоит в том, что агрегация является более слабой связью.

Поэтому представления UML также очень похожи. Единственная разница в том, что ромб пуст:

./43f0f427947d6e785a50d8e38f82a5e7.png

Тогда для автомобилей и колес мы бы сделали:

./245a6dd6dc64967a61447be8b2b15025.png

3.2. Исходный код

В Java мы можем смоделировать агрегацию с помощью старой простой ссылки:

class Wheel {}

class Car {
List<Wheel> wheels;
}

Член может быть классом любого типа, кроме нестатического внутреннего класса.

В приведенном выше фрагменте кода оба класса имеют отдельный исходный файл. Однако мы также можем использовать статический внутренний класс:

class Car {
List<Wheel> wheels;
static class Wheel {}
}

Обратите внимание, что Java создаст неявную ссылку только в нестатических внутренних классах. Из-за этого мы должны поддерживать отношения вручную там, где нам это нужно:

class Wheel {
Car car;
}

class Car {
List<Wheel> wheels;
}

4. Ассоциация

Ассоциация - самая слабая связь между тремя. Это не отношение «имеет» , ни один из объектов не является частью или членом другого.

Ассоциация означает только то, что объекты «знают» друг друга. Например, мать и ее ребенок.

4.1. UML

В UML мы можем пометить ассоциацию стрелкой:

./e160ea1a83442180588425bf0a193a2d.png

Если ассоциация двунаправленная, мы можем использовать две стрелки, стрелку со стрелкой на обоих концах или линию без стрелок:

./0a4dc8fbd24efc57fc09ed01f1cfb090.png

Мы можем представить мать и ее ребенка в UML, тогда:

./f3844b36507379694edfa555d505af56.png

4.2. Исходный код

В Java мы можем моделировать ассоциацию так же, как агрегацию:

class Child {}

class Mother {
List<Child> children;
}

Но подождите, как мы можем определить, означает ли ссылка агрегацию или ассоциацию?

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

Кроме того, мы должны поддерживать ссылки вручную на обоих концах, как мы это делали с агрегацией:

class Child {
Mother mother;
}

class Mother {
List<Child> children;
}

5. Дополнительная информация об UML

Ради ясности иногда мы хотим определить кардинальность отношения на диаграмме UML. Мы можем сделать это, написав его на концах стрелки:

./d0718ebbd6c9de51bc6548ede3f6f17d.png

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

./c24e27b3a09e5b4b192f02997816ad13.png

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

6. Сложный пример

Давайте посмотрим (немного) более сложный пример!

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

Будут ли кафедры существовать после того, как мы закроем университет? Конечно нет, поэтому это композиция.

Но профессора останутся (надеюсь). Мы должны решить, что логичнее: считать профессоров частью кафедр или нет. Альтернативно: являются они членами отделов или нет? Да. Следовательно, это совокупность. Кроме того, профессор может работать в нескольких отделах.

Отношения между профессорами являются ассоциативными, потому что нет никакого смысла говорить, что профессор является частью другого.

В результате мы можем смоделировать этот пример с помощью следующей диаграммы UML:

./3622368419956437603674f7fea84cc0.png

И код Java выглядит так:

class University {
List<Department> department;
}

class Department {
List<Professor> professors;
}

class Professor {
List<Department> department;
List<Professor> friends;
}

Обратите внимание, что если мы полагаемся на термины «имеет», «принадлежит», «член», «часть» и т. д., мы можем легче определить отношения между нашими объектами.

7. Заключение

В этой статье мы увидели свойства и представление композиции, агрегации и ассоциации. Мы также увидели, как моделировать эти отношения в UML и Java.

Как обычно, примеры доступны на GitHub .