Композиция против наследования
Часть раздела Объектно-ориентированное программирование путешествия по 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», потому что это приводит к более гибкому и поддерживаемому коду.
Задание
ЛегкоДавайте создадим компьютерную систему с использованием композиции, чтобы продемонстрировать отношение "has-a". Вместо того чтобы Computer наследовал свои компоненты (что не имело бы смысла), ваш компьютер будет содержать необходимые для работы части.
Вы организуете код в трёх файлах:
Processor.java: создайте класс, представляющий CPU. У Processor будут два закрытых поля:brand(String) иspeedGHz(double). Добавьте constructor для инициализации обоих полей и getter methods для каждого из них. Добавьте методprocess(), который возвращает String:[brand] processing at [speedGHz] GHzMemory.java: создайте класс, представляющий RAM. У Memory будут два закрытых поля:type(String, например "DDR4" или "DDR5") иsizeGB(int). Добавьте constructor, getter methods и методload(), который возвращает:Loading [sizeGB]GB [type] memoryComputer.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()
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово thisМетодыПоля (атрибуты)Метод-конструкторПерегрузка конструкторовИтоги — Простой калькулятор4Наследование
Основы наследования (extends)Ключевое слово superПереопределение методов (@Override)Цепочка конструкторовКласс ObjectОдиночное и многоуровневое наследованиеПочему нет множественного наследования классовИтоги — Иерархия сотрудников7Специальные методы и класс Object
Метод toString()equals() и hashCode()Метод clone()compareTo() и ComparableИнтерфейс ComparatorПовторение — Кастомная сортировка2Модификаторы доступа и инкапсуляция
Обзор уровней доступаМетоды геттеры и сеттерыСокрытие данныхКлючевое слово finalИтоги — Менеджер банковского счета5Полиморфизм
Основы перегрузки методовПереопределение методов (Run-Time)Upcasting и DowncastingОператор instanceofАбстрактные классы и методыИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияАгрегация против композицииВнутренние, вложенные и анонимные классыEnums и методы перечисленийRecords (Java 16+)Sealed-классы (Java 17+)11Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy3Свойства класса и статические члены
Переменные экземпляра и статические переменныеСтатические методыСтатические блокиКонстанты (static final)Итоги: Счетчик и утилиты6Интерфейсы и абстрактные классы
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовDefault и Static методы в интерфейсахАбстрактные классы vs ИнтерфейсыФункциональные интерфейсыИтоги — Платежная система9Обобщения
Введение в обобщенияОбобщенные классыОбобщенные методыОграниченные параметры типаСимволы подстановки (Wildcards: ?, extends, super)Итоги — Обобщенный контейнер12Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»Потренируйтесь самостоятельно: Онлайн-компилятор Java