Рубрики
Без рубрики

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

Изучите свойства и представление композиции, агрегации и ассоциации в Java.

Автор оригинала: Attila Fejér.

1. введение

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

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

2. Состав

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

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

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

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

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

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

2.1. UML

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

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

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

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 rooms;
    class Room {}   
}

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

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

3. Агрегация

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

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

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

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

3.1. UML

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

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

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

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

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

class Wheel {}

class Car {
    List wheels;
}

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

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

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

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

class Wheel {
    Car car;
}

class Car {
    List wheels;
}

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

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

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

4.1. UML

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

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

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

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

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

class Child {}

class Mother {
    List children;
}

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

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

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

class Child {
    Mother mother;
}

class Mother {
    List children;
}

5. UML Sidenote

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

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

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

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

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

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

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

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

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

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

И код Java выглядит следующим образом:

class University {
    List department;   
}

class Department {
    List professors;
}

class Professor {
    List department;
    List friends;
}

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

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

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

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