Skip to content
IRC-CodingIRC-Coding
Отношения между классамиАссоциацияАгрегацияКомпозицияНаследование

ООП Отношения между классами: ассоциация, агрегация, композиция

ООП отношения между классами: ассоциация, агрегация, композиция, наследование с практическими примерами и UML-нотацией.

S

schutzgeist

6 min read
ООП Отношения между классами: ассоциация, агрегация, композиция

Отношения между классами в 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-нотация: разные стрелки и символы ромбов
  • Жизненный цикл: зависимость объектов друг от друга
  • Архитектура: важно для проектирования программного обеспечения

Основные компоненты

  1. Ассоциация: двусторонние или односторонние отношения
  2. Агрегация: слабое отношение “has-a”
  3. Композиция: сильное отношение “is-part-of”
  4. Наследование: специализация и переиспользование кода
  5. Кратность: количество экземпляров в отношении
  6. Роли: обозначение роли в отношении
  7. Навигируемость: направление отношения
  8. Квалификаторы: дополнительная информация об отношении

Практические примеры

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. В чем разница между агрегацией и композицией? Агрегация: части могут существовать независимо. Композиция: части существуют только с целым.

  2. Объясните мультипликативность 1..*! Минимум один объект, максимум неограниченное количество связанных объектов.

  3. Когда использовать наследование вместо композиции? Когда есть отношение «является» и нужна переиспользование кода.

  4. Что означает двусторонняя ассоциация? Оба класса знают друг о друге и могут друг к другу обращаться.

Ключевые источники

  1. https://de.wikipedia.org/wiki/Assoziation_(UML)
  2. https://refactoring.guru/design-patterns/composition-over-inheritance
  3. https://www.uml-diagrams.org/class-diagram-relationships.html

Далее в курсе OOP

Следующая статья курса рассматривает OOP Vererbung: Grundlagen, Inheritance & Polymorphie — как наследование позволяет переиспользовать код.

Назад к блогу
Share:

Nächster Artikel in Разработка программного обеспечения

Weiterlesen
OOP Классы: Static vs Instance методы и атрибуты

Похожие статьи