Композиция против наследования
Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy. Урок 82 из 110.
Вы узнали, что наследование создаёт отношение «является» — Dog — это Animal. Но есть и другой мощный подход: композиция, которая создаёт отношение «имеет». Вместо наследования поведения класс содержит экземпляры других классов и делегирует им работу.
Рассмотрим класс Car. При наследовании можно попробовать унаследовать класс Engine — но автомобиль не является двигателем, у него есть двигатель:
class Engine {
void start() => print('Engine started');
}
class Car {
final Engine _engine = Engine(); // Композиция: у Car ЕСТЬ Engine
void start() {
_engine.start(); // Делегировать вызов двигателю
print('Car is ready to drive');
}
}
void main() {
var car = Car();
car.start();
}Ключевое преимущество композиции — гибкость. При наследовании ты ограничен одним родительским классом. При композиции можно объединять несколько компонентов и даже заменять их во время выполнения программы:
class ElectricEngine {
void start() => print('Electric motor humming');
}
class HybridCar {
Engine? _gasEngine;
ElectricEngine? _electricEngine;
HybridCar({bool useElectric = false}) {
if (useElectric) {
_electricEngine = ElectricEngine();
} else {
_gasEngine = Engine();
}
}
}Общее правило — «отдавайте предпочтение композиции, а не наследованию». Используйте наследование, когда есть истинное отношение «является» и вам нужен полиморфизм. Используйте композицию, когда хотите повторно использовать поведение без сильной связанности или когда объекту нужны возможности из нескольких источников.
Задание
ЛегкоДавайте создадим компьютерную систему с помощью композиции! Вместо того чтобы пытаться заставить компьютер наследовать свои части, вы создадите отдельные классы компонентов и объедините их — ведь у компьютера есть CPU и есть память, это не CPU и не память.
Вы организуете код в три файла:
components.dart: создайте два класса компонентов, представляющих части компьютера:- Класс
CPUс полямиString brandиdouble speedGHz. Добавьте методprocess(), который возвращает строку:[brand] processing at [speedGHz]GHz - Класс
Memoryс полемint sizeGB. Добавьте методload(), который возвращает строку:Loading [sizeGB]GB of data
- Класс
computer.dart: создайте классComputer, который использует композицию для объединения компонентов. Ваш компьютер должен:- Иметь поле
String name - Содержать экземпляры
CPUиMemory(композиция — у компьютера ЕСТЬ эти части) - Принимать все необходимые значения через конструктор, чтобы создавать оба компонента внутри класса
- Иметь метод
boot(), который делегирует работу обоим компонентам и выводит три строки: имя компьютера, за которым следуетstarting..., затем результат вызова метода process у CPU, а затем результат вызова метода load у Memory - Иметь метод
specs(), который возвращает строку с описанием системы:[name]: [brand] [speedGHz]GHz, [sizeGB]GB RAM
- Иметь поле
main.dart: импортируйте свои файлы и продемонстрируйте композицию в действии:- Создайте
Computerс именемWorkstation, с процессоромIntelс частотой3.5GHz и16GB памяти - Вызовите метод
boot() - Выведите результат вызова
specs()
- Создайте
Обратите внимание, что класс Computer не расширяет CPU или Memory — он содержит их и делегирует им выполнение работы. Такую связь «имеет» и создаёт композиция!
Ожидаемый вывод:
Workstation starting...
Intel processing at 3.5GHz
Loading 16GB of data
Workstation: Intel 3.5GHz, 16GB RAMПопробуйте сами
import 'computer.dart';
void main() {
// TODO: Создайте Computer с именем "Workstation" со следующими характеристиками:
// - процессор Intel с частотой 3.5 GHz
// - 16 GB памяти
// TODO: Вызовите метод boot()
// TODO: Выведите результат specs()
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыБиблиотеки и импортВведение в ООПКлассы и объектыКлючевое слово thisМетодыПеременные экземпляраОсновы конструкторовИтоги — Простой калькулятор4Null-безопасность
Введение в Null-безопасностьNullable и Non-Nullable типыОператоры ? и !Ключевое слово Late и Null-безопасностьNull-aware операторыNull-безопасность в классахИтоги — Система профилей пользователей7Абстрактные классы и интерфейсы
Абстрактные классыАбстрактные методыИнтерфейсы в DartНеявные интерфейсыРеализация против наследованияМножественные интерфейсыИтоги — Калькулятор фигур10Коллекции и обобщения
Обзор List, Set, MapТипобезопасные коллекцииОбобщенные классыОбобщенные методыОграничения обобщенийIterable и IteratorИтоги: Обобщенное хранилище13Продвинутые концепции ООП
Композиция против наследованияМетоды расширенияВызываемые классыЗапечатанные классы (Dart 3)Записи (Dart 3)Паттерны и сопоставление (3.0)Перечисления с методами16Проект: Управление библиотекой
Обзор проектаКлассы Book и User2Конструкторы в Dart
Конструктор по умолчаниюИменованные конструкторыСписки инициализацииКонстантные конструкторыФабричные конструкторыПеренаправляющие конструкторыИтоги — Shape Builder5Инкапсуляция
Публичные и приватные членыСоглашение о префиксе _Приватность на уровне библиотекГеттеры и сеттеры: подробный разборСкрытие данныхИтоги — Записи о студентах8Mixins
Введение в MixinsСоздание MixinsИспользование нескольких MixinsКлючевое слово on в MixinsMixin и наследованиеMixin и интерфейсИтоги: Система животных11Специальные методы
Переопределение toString()Переопределение hashCode и ==Интерфейс ComparableМетод call()Переопределение noSuchMethodПовторение — Пользовательская коллекция14Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy3Свойства классов
Члены экземпляра и статические членыПоля Final и ConstПеременные LateСтатические методы и поляГеттеры и сеттерыИтоги — Менеджер банковского счета6Наследование
Основы наследованияКлючевое слово superПереопределение методовАннотация @overrideКлючевое слово final для классовКонструкторы и наследованиеИтоги: Иерархия сотрудников9Полиморфизм
Основы полиморфизмаПолиморфизм через интерфейсыПроверка типов во время выполненияОператоры is и asКлючевое слово covariantИтоги — Обработчик платежей12Асинхронное ООП
Futures и async/awaitОсновы StreamsКонтроллеры StreamАсинхронные конструкторыАсинхронность в методах классовИтоги — Data Fetcher15Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн РепозиторийПотренируйтесь самостоятельно: Онлайн-компилятор Dart