Ассоциация, агрегация и композиция
Большая часть классов приложения связаны между собой. В этом разделе рассмотрим две классификации отношений между классами.
1. Отношения, основанные на иерархии классов
Давайте начнем с отношений, основанных на иерархии классов - это отношения IS-A и HAS-A.

1.1. IS-A отношения
В ООП принцип IS-A основан на наследовании классов или реализации интерфейсов.
Например, если класс HeavyBox наследует Box, мы говорим, что HeavyBox является Box (HeavyBox IS-A Box):

class Box {
private double width;
private double height;
private double depth;
…
}
class HeavyBox extends Box {
private int weight;
…
} Или другой пример - класс Lorry расширяет класс Car:

class Car {
…
}
class Lorry extends Car {
…
} В этом случае Lorry IS-A Car.
Другой пример - классы Person и Driver:

class Person {
…
}
class Driver extends Person {
…
} Driver IS-A Person.
То же самое относится и к реализации интерфейсов. Если класс Transport реализует интерфейс Moveable, то они находятся в отношении Transport IS-A Moveable:

interface Moveable {
…
}
class Transport implements Moveable{
…
} 1.2. HAS-A отношения
HAS-A отношения основаны на использовании.
Например, класс Car содержит переменную типа Engine:

class Car {
Engine engine;
…
}
class Engine{
…
} Мы говорим Car HAS-A Engine.
Или Shop содержит массив Category:

class Shop {
Category [] categories;
…
}
class Category {
…
} Это тоже отношение HAS-A: Shop HAS-A Category.
2. Более детализированная классификация взаимодействий
Выделяют также следующие взаимоотношения между классами - ассоциация, агрегация и композиция.

2.1. Ассоциация
Начнем с ассоциации. В этих отношениях объекты двух классов могут ссылаться друг на друга. Ассоциация может быть односторонней или двусторонней.
Например, класс Person содержит переменную Car, в этом случае мы можем говорить, что есть ассоциация между классом Person и Car:

class Person {
private String name;
private Car car;
…
}
class Car {
…
} Агрегация и композиция являются частными случаями ассоциации. Агрегация - отношение когда один объект является частью другого. А композиция - еще более тесная связь, когда объект не только является частью другого объекта, но и вообще не может принадлежать другому объекту. Разница будет понятна при рассмотрении реализации этих отношений.
2.2. Агрегация
Объект класса Keyboard создается извне PC и передается в конструктор для установления связи. Если объект класса PC будет удален, объект класса Keyboard может и дальше использоваться, если, конечно, на него останется ссылка:

public class PC {
private Keyboard keyboard;
public PC (Keyboard keyboard) {
this.keyboard = keyboard;
}
} 2.3. Композиция
Теперь посмотрим на реализацию композиции. Объект класса Keyboard создается в конструкторе, что означает более тесную связь между объектами. Объект класса Keyboard не может существовать без создавшего его объекта PC:

public class PC {
private Keyboard keyboard;
public PC () {
this.keyboard = new Keyboard();
}
} 3. Композиция против Наследования (Composition over Inheritance)
В объектно-ориентированном проектировании существует важное правило: отдавайте предпочтение композиции перед наследованием.
Почему наследование может быть опасным?
Хотя наследование — мощный инструмент, чрезмерное его использование приводит к проблемам:
-
Хрупкость кода: Если вы измените логику в базовом классе, это может неожиданно «сломать» десятки наследников.
-
Нарушение инкапсуляции: Подкласс часто зависит от деталей реализации родителя.
-
Жесткая иерархия: Вы не можете изменить родителя во время выполнения программы.
-
Проблема «множественного наследования»: В Java нельзя наследоваться от двух классов сразу. Если вам нужны функции и «Принтера», и «Сканера», вы не сможете создать «МФУ» через наследование.
Представим, что мы создаем систему для управления персоналом.
Плохой подход (Наследование):
class Employee {}
class Driver extends Employee {} // А если водитель станет менеджером? Нам придется создавать новый объект. Хороший подход (Композиция):
Вместо того чтобы говорить, что сотрудник является водителем, мы скажем, что у сотрудника есть роль.
class Role {}
class DriverRole extends Role {}
class ManagerRole extends Role {}
class Employee {
private Role role; // Композиция: сотрудник "имеет" роль
public void setRole(Role role) {
this.role = role; // Мы можем сменить роль в любой момент!
}
} Когда что выбирать?
-
Используйте наследование, только если подкласс является полной и логической заменой родителя (принцип подстановки Лисков). Если вы чувствуете, что наследуете класс только ради того, чтобы «подсмотреть» его методы — используйте композицию.
-
Используйте композицию, если вы хотите создавать сложные объекты из простых «кирпичиков» и иметь возможность менять поведение программы на лету.
Важное предостережение: Глубокие иерархии наследования (более 3-х уровней) делают код практически нечитаемым и очень сложным в тестировании. Чем меньше уровней в вашей «родословной» классов, тем стабильнее ваша система.
Презентацию с видео можно скачать на Patreon.
Please log in or register to have a possibility to add comment.