Menu
Coddy logo textTech

Композиция против наследования

Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy. Урок 47 из 87.

Наследование создаёт отношение «is-a»: Dog является Animal. Но что, если классу нужна функциональность от чего-то, чем он не является? Здесь и применяется composition, устанавливая вместо этого отношение «has-a».

При использовании Composition класс содержит экземпляры других классов в качестве полей, а не наследует их:

// Подход с наследованием: Car IS an Engine (не имеет смысла)
class Car extends Engine { }

// Подход с композицией: Car HAS an Engine (имеет смысл)
class Car {
    private Engine engine;
    
    public Car(Engine engine) {
        this.engine = engine;
    }
    
    public void start() {
        engine.ignite();
    }
}

Composition предоставляет несколько преимуществ. Ты можешь изменить составной объект во время выполнения программы, например заменить engine.

Вы также можете объединять поведение нескольких классов — это невозможно при использовании Inheritance в Java. Составной класс предоставляет только выбранные им методы, обеспечивая лучшую инкапсуляцию.

Практическое руководство: используйте наследование, когда действительно существует отношение «является» и подкласс действительно представляет специализированную версию родительского класса.

Используйте composition, когда вам нужно использовать функциональность другого класса, не являясь объектом этого типа. Многие опытные разработчики следуют принципу: «предпочитайте composition Inheritance», потому что это приводит к более гибкому и поддерживаемому коду.

challenge icon

Задание

Легко

Давайте создадим компьютерную систему с использованием композиции, чтобы продемонстрировать отношение "has-a". Вместо того чтобы Computer наследовал свои компоненты (что не имело бы смысла), ваш компьютер будет содержать необходимые для работы части.

Вы организуете код в трёх файлах:

  • Processor.java: создайте класс, представляющий CPU. У Processor будут два закрытых поля: brand (String) и speedGHz (double). Добавьте constructor для инициализации обоих полей и getter methods для каждого из них. Добавьте метод process(), который возвращает String: [brand] processing at [speedGHz] GHz
  • Memory.java: создайте класс, представляющий RAM. У Memory будут два закрытых поля: type (String, например "DDR4" или "DDR5") и sizeGB (int). Добавьте constructor, getter methods и метод load(), который возвращает: Loading [sizeGB]GB [type] memory
  • Computer.java: именно здесь композиция раскрывается во всей красе! Ваш класс Computer должен иметь Processor и Memory в качестве закрытых полей. Он не расширяет их, а содержит их. Добавьте constructor, который принимает оба компонента. Добавьте метод boot(), возвращающий многострочный String, показывающий запуск компьютера с использованием его компонентов:
    Booting computer...
    [processor.process() result]
    [memory.load() result]
    System ready!
    Также добавьте метод getSpecs(), который возвращает: Specs: [processor brand] CPU, [memory sizeGB]GB [memory type]
  • Main.java: оживите вашу составную систему! Вы получите четыре входных значения: brand процессора (String), скорость процессора (double), type памяти (String) и размер памяти (int).

    Создайте Processor и Memory с этими значениями, затем объедините их в Computer. Выведите результат вызова boot(), затем выведите результат getSpecs().

Вы получите четыре входных значения в следующем порядке: brand процессора, скорость процессора (GHz), type памяти и размер памяти (GB).

Обратите внимание, как Computer делегирует работу своим компонентам, вместо того чтобы пытаться быть ими. В этом и заключается сила композиции! Computer может использовать любой Processor или Memory, которые вы ему передадите, что делает дизайн гибким и реалистичным.

Попробуйте сами

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Прочитать входные данные
        String processorBrand = scanner.nextLine();
        double processorSpeed = Double.parseDouble(scanner.nextLine());
        String memoryType = scanner.nextLine();
        int memorySize = Integer.parseInt(scanner.nextLine());
        
        // TODO: Создать Processor с брендом и скоростью
        
        // TODO: Создать Memory с типом и размером
        
        // TODO: Создать Computer, скомпоновав Processor и Memory
        
        // TODO: Вывести результат boot()
        
        // TODO: Вывести результат getSpecs()
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Java