«1. Введение

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

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

2. Композиция

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

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

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

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

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

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

2.1. UML

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

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

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

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-представления также очень похожи. Единственная разница в том, что ромб пуст:

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

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

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

class Wheel {}

class Car {
    List<Wheel> wheels;
}

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

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

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

«

class Wheel {
    Car car;
}

class Car {
    List<Wheel> wheels;
}

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

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

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

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

4.1. UML

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

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

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

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

class Child {}

class Mother {
    List<Child> children;
}

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

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

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

class Child {
    Mother mother;
}

class Mother {
    List<Child> children;
}

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

5. UML Sidenote

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

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

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

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

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

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

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

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

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

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

class University {
    List<Department> department;   
}

class Department {
    List<Professor> professors;
}

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

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

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

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

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