Отношения между классами в OOP: ассоциация, агрегация, композиция и наследование
В этой статье разберём отношения между классами в объектно-ориентированном программировании с ассоциацией, агрегацией, композицией и наследованием на практических примерах.
Суть вопроса
Отношения между классами описывают, как объекты взаимодействуют и сотрудничают друг с другом, образуя сложные системы. Есть четыре основных типа отношений: ассоциация, агрегация, композиция и наследование.
Определение
Отношения между классами определяют способ связи классов и их объектов между собой. Они критичны для структурирования объектно-ориентированных систем.
Четыре основных типа отношений:
1. Ассоциация (has-a)
- Определение: Две класса связаны между собой
- Жизненный цикл: Независимы друг от друга
- Пример:
КлиентимеетЗаказы - UML: простая линия между классами
2. Агрегация (consists-of)
- Определение: отношение “has-a”, части могут существовать независимо
- Жизненный цикл: Часть может существовать без целого
- Пример:
АвтомобильимеетШины - UML: линия с пустым ромбом у целого
3. Композиция (is-part-of)
- Определение: отношение “is-part-of”, части существуют только с целым
- Жизненный цикл: Часть не может существовать без целого
- Пример:
АвтомобильимеетДвигатель - UML: линия с заполненным ромбом у целого
4. Наследование (is-a)
- Определение: отношение “is-a”, специализация
- Жизненный цикл: Подкласс наследует от суперкласса
- Пример:
Легковой автомобильявляетсяТранспортным средством - UML: линия со стрелкой к суперклассу
Ключевые моменты
- Ассоциация: простое отношение между классами
- Агрегация: отношение “has-a” с независимыми частями
- Композиция: отношение “is-part-of” с зависимыми частями
- Наследование: отношение “is-a” со специализацией
- Кратность: 1, , 0..1, 1.., 0..*
- UML-нотация: разные стрелки и символы ромбов
- Жизненный цикл: зависимость объектов друг от друга
- Архитектура: важно для проектирования программного обеспечения
Основные компоненты
- Ассоциация: двусторонние или односторонние отношения
- Агрегация: слабое отношение “has-a”
- Композиция: сильное отношение “is-part-of”
- Наследование: специализация и переиспользование кода
- Кратность: количество экземпляров в отношении
- Роли: обозначение роли в отношении
- Навигируемость: направление отношения
- Квалификаторы: дополнительная информация об отношении
Практические примеры
1. Ассоциация (Клиент - Заказ)
// Двусторонняя ассоциация
public class Клиент {
private String клиентId;
private String имя;
private List<Заказ> заказы = new ArrayList<>();
public void addЗаказ(Заказ заказ) {
заказы.add(заказ);
заказ.setКлиент(this); // Обратная ссылка
}
public List<Заказ> getЗаказы() {
return new ArrayList<>(заказы);
}
}
public class Заказ {
private String заказId;
private Date датаЗаказа;
private Клиент клиент; // Обратная ссылка на клиента
public void setКлиент(Клиент клиент) {
this.клиент = клиент;
}
public Клиент getКлиент() {
return клиент;
}
}
// Использование
Клиент иванов = new Клиент("1", "Иванов");
Заказ з1 = new Заказ("З001", new Date());
Заказ з2 = new Заказ("З002", new Date());
иванов.addЗаказ(з1);
иванов.addЗаказ(з2);
2. Агрегация (Автомобиль - Шины)
// Агрегация: Автомобиль имеет шины, шины могут существовать без автомобиля
public class Автомобиль {
private String модель;
private List<Шина> шины = new ArrayList<>();
public Автомобиль(String модель) {
this.модель = модель;
}
public void addШину(Шина шина) {
if (шины.size() < 4) {
this.шины.add(шина);
}
}
public void removeШину(Шина шина) {
this.шины.remove(шина);
// Шина продолжает существовать и может быть назначена другому автомобилю
}
}
public class Шина {
private String производитель;
private int размер;
public Шина(String производитель, int размер) {
this.производитель = производитель;
this.размер = размер;
}
// Шина может существовать независимо от автомобиля
public void установить() {
System.out.println("Шина устанавливается");
}
}
// Использование
Автомобиль golf = new Автомобиль("Golf");
Шина michelin1 = new Шина("Michelin", 195);
Шина michelin2 = new Шина("Michelin", 195);
golf.addШину(michelin1);
golf.addШину(michelin2);
// Шины можно снять и переиспользовать
golf.removeШину(michelin1);
3. Композиция (Автомобиль - Двигатель)
// Композиция: Двигатель существует только с автомобилем
public class Автомобиль {
private String модель;
private Двигатель двигатель; // Двигатель не может существовать без автомобиля
public Автомобиль(String модель, int мощность) {
this.модель = модель;
this.двигатель = new Двигатель(мощность); // Двигатель создаётся внутри
}
public void стартовать() {
двигатель.стартовать();
System.out.println(модель + " запускается");
}
public void выключить() {
двигатель.выключить();
System.out.println(модель + " выключается");
}
// Двигатель удаляется вместе с автомобилем
protected void finalize() {
// Двигатель автоматически очищается
}
}
public class Двигатель {
private int мощность;
private boolean работает;
// Приватный конструктор - только Автомобиль может создать двигатель
protected Двигатель(int мощность) {
this.мощность = мощность;
this.работает = false;
}
protected void стартовать() {
this.работает = true;
System.out.println("Двигатель с " + мощность + " л.с. запускается");
}
protected void выключить() {
this.работает = false;
System.out.println("Двигатель выключается");
}
}
// Использование
Автомобиль bmw = new Автомобиль("BMW", 200);
bmw.стартовать(); // Двигатель запускается внутри
bmw.выключить();
// Двигатель нельзя создать независимо:
// Двигатель двигатель = new Двигатель(150); // Ошибка: конструктор protected
4. Наследование (Транспортное средство - Автомобиль)
// Суперкласс
public abstract class ТранспортноеСредство {
protected String марка;
protected int годВыпуска;
protected int текущаяСкорость = 0;
public ТранспортноеСредство(String марка, int годВыпуска) {
this.марка = марка;
this.годВыпуска = годВыпуска;
}
// Общие методы
public void ускориться(int кмч) {
this.текущаяСкорость += кмч;
System.out.println(марка + " ускоряется до " + текущаяСкорость + " км/ч");
}
public void затормозить(int кмч) {
if (текущаяСкорость >= кмч) {
this.текущаяСкорость -= кмч;
System.out.println(марка + " замедляется до " + текущаяСкорость + " км/ч");
}
}
// Абстрактный метод - должен быть реализован подклассами
public abstract void посигналить();
// Конкретный метод - может быть переопределён
public void вывести() {
System.out.println("Транспорт: " + марка + ", год: " + годВыпуска +
", скорость: " + текущаяСкорость + " км/ч");
}
}
// Подкласс
public class Автомобиль extends ТранспортноеСредство {
private int количествоДверей;
private boolean климатизация;
public Автомобиль(String марка, int годВыпуска, int количествоДверей) {
super(марка, годВыпуска); // Вызов конструктора суперкласса
this.количествоДверей = количествоДверей;
this.климатизация = false;
}
// Реализация абстрактного метода
@Override
public void посигналить() {
System.out.println("Машина сигналит: Бип-бип!");
}
// Дополнительный метод только для Автомобиля
public void включитьКлиматизацию() {
климатизация = true;
System.out.println("Климатизация включена");
}
// Переопределение метода суперкласса
@Override
public void вывести() {
super.вывести(); // Вызов метода суперкласса
System.out.println(" Тип: Автомобиль, двери: " + количествоДверей +
", климатизация: " + (климатизация ? "да" : "нет"));
}
}
// Ещё один подкласс
public class Мотоцикл extends ТранспортноеСредство {
private boolean имеетКоляску;
public Мотоцикл(String марка, int годВыпуска, boolean имеетКоляску) {
super(марка, годВыпуска);
this.имеетКоляску = имеетКоляску;
}
@Override
public void посигналить() {
System.out.println("Мотоцикл сигналит: Бррр!");
}
public void вилли() {
System.out.println("Мотоцикл делает вилли!");
}
@Override
public void вывести() {
super.вывести();
System.out.println(" Тип: Мотоцикл, коляска: " +
(имеетКоляску ? "да" : "нет"));
}
}
// Использование
ТранспортноеСредство golf = new Автомобиль("Volkswagen", 2023, 5);
ТранспортноеСредство harley = new Мотоцикл("Harley-Davidson", 2022, false);
golf.посигналить(); // Машина сигналит: Бип-бип!
harley.посигналить(); // Мотоцикл сигналит: Бррр!
golf.ускориться(50);
harley.ускориться(80);
golf.вывести();
harley.вывести();
// Приведение типов для специфических методов
if (golf instanceof Автомобиль) {
Автомобиль autoGolf = (Автомобиль) golf;
autoGolf.включитьКлиматизацию();
}
if (harley instanceof Мотоцикл) {
Мотоцикл motoHarley = (Мотоцикл) harley;
motoHarley.вилли();
}
UML-нотация для отношений между классами
Ассоциация
Kunde 1..* --* Bestellung
Агрегация
Auto 1 --* Reifen
Композиция
Auto 1 --* Motor
Наследование
Fahrzeug <|-- Auto
Fahrzeug <|-- Motorrad
Мультипликативность
| Символ | Значение | Пример |
|---|---|---|
| 1 | Ровно один | 1 Motor |
| 0..1 | Ноль или один | 0..1 Führerschein |
| * | Ноль или больше | * Reifen |
| 1..* | Минимум один | 1..* Türen |
| 2..4 | От двух до четырех | 2..4 Räder |
Выбор нужного типа отношения
Какое отношение использовать?
Ассоциация подходит когда:
- Два класса взаимодействуют, но между ними нет зависимости
- Отношение носит временный или необязательный характер
- Объекты существуют независимо друг от друга
Агрегация подходит когда:
- Между объектами отношение «имеет»
- Части могут существовать без целого
- Части можно использовать в разных целых объектах
Композиция подходит когда:
- Между объектами отношение «часть от»
- Части существуют только как часть целого
- Жизненный цикл части привязан к жизненному циклу целого
Наследование подходит когда:
- Между классами отношение «является»
- Подкласс представляет специальный случай родительского класса
- Нужна переиспользование кода и полиморфизм
Плюсы и минусы
Преимущества отношений между классами
- Структурированность: четкая архитектура системы
- Переиспользование: общая функциональность в одном месте
- Гибкость: просто расширять и модифицировать код
- Понятность: отражаем реальный мир в коде
- Поддерживаемость: целевые изменения без побочных эффектов
Недостатки
- Сложность: много отношений могут запутать архитектуру
- Связанность: сильные зависимости приводят к проблемам
- Производительность: много связей между объектами замедляют работу
- Тестируемость: сложные отношения трудно тестировать
Типичные вопросы на экзаменах
-
В чем разница между агрегацией и композицией? Агрегация: части могут существовать независимо. Композиция: части существуют только с целым.
-
Объясните мультипликативность 1..*! Минимум один объект, максимум неограниченное количество связанных объектов.
-
Когда использовать наследование вместо композиции? Когда есть отношение «является» и нужна переиспользование кода.
-
Что означает двусторонняя ассоциация? Оба класса знают друг о друге и могут друг к другу обращаться.
Ключевые источники
- https://de.wikipedia.org/wiki/Assoziation_(UML)
- https://refactoring.guru/design-patterns/composition-over-inheritance
- https://www.uml-diagrams.org/class-diagram-relationships.html
Далее в курсе OOP
Следующая статья курса рассматривает OOP Vererbung: Grundlagen, Inheritance & Polymorphie — как наследование позволяет переиспользовать код.



