Композиция против наследования
Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 56 из 91.
В предыдущей главе вы узнали, что трейты обеспечивают возможность «has-a» (имеет), в то время как наследование устанавливает отношение «is-a» (является). Композиция развивает концепцию «has-a» дальше, создавая сложные объекты из более простых и сохраняя их в качестве свойств, а не наследуя от них.
При наследовании Car может расширять Vehicle. При композиции Car содержит объект Engine:
<?php
class Engine {
public function start(): string {
return "Engine started";
}
}
class Car {
private Engine $engine;
public function __construct() {
$this->engine = new Engine();
}
public function start(): string {
return $this->engine->start();
}
}
$car = new Car();
echo $car->start();
Вывод:
Engine startedКласс Car не наследуется от Engine — он владеет им. Этот подход дает значительные преимущества: вы можете заменить двигатель на другую реализацию, классы остаются слабо связанными, и вы избегаете жестких иерархий, которые создает глубокое наследование.
Общим правилом в ООП является "предпочитайте композицию наследованию." Наследование хорошо работает для подлинных иерархий типов (Dog действительно является Animal), но композиция проявляет себя лучше всего, когда вы собираете функциональность из независимых частей. Вы можете комбинировать несколько объектов, заменять их во время выполнения и тестировать компоненты изолированно.
В следующем уроке рассматривается внедрение зависимостей, которое напрямую основывается на композиции, управляя тем, как объекты получают свои зависимости.
Задание
ЛегкоДавайте создадим компьютерную систему, используя композицию, где Computer собирается из отдельных объектов-компонентов, а не наследуется от них. Это демонстрирует, как композиция позволяет создавать сложные объекты из более простых, независимых частей.
Вы организуете свой код в трех файлах:
Processor.php— Создайте классProcessor, который представляет центральный процессор. Используйте продвижение свойств в конструкторе (constructor promotion) для определения приватного свойства$model(string) и приватного свойства$cores(integer). Добавьте методgetSpecs(), который возвращает"{$this->model} ({$this->cores} cores)".Memory.php— Создайте классMemory, который представляет оперативную память. Используйте продвижение свойств в конструкторе для определения приватного свойства$size(integer, представляющее ГБ). Добавьте методgetCapacity(), который возвращает"{$this->size}GB RAM".main.php— Подключите оба файла компонентов и создайте классComputer, использующий композицию. КлассComputerдолжен:- Хранить объект
Processorи объектMemoryв качестве приватных свойств - Принимать модель процессора, количество ядер и объем памяти в своем конструкторе
- Создавать объекты компонентов внутри себя во время инициализации
- Иметь метод
describe(), который возвращает"Computer with " . $this->processor->getSpecs() . " and " . $this->memory->getCapacity()
Вы получите три входных значения: модель процессора, количество ядер и объем памяти в ГБ. Создайте объект
Computerс этими характеристиками и выведите результат вызова методаdescribe().- Хранить объект
Обратите внимание, что Computer не расширяет Processor или Memory — он содержит их. Эта связь «имеет» (has-a) через композицию сохраняет каждый компонент независимым и пригодным для повторного использования, позволяя Computer делегировать задачи своим частям при необходимости.
Шпаргалка
Композиция строит сложные объекты из более простых, сохраняя их как свойства, а не наследуя от них. Это создает отношение «имеет» (has-a), при котором один класс содержит экземпляры других классов.
Базовый пример композиции:
<?php
class Engine {
public function start(): string {
return "Engine started";
}
}
class Car {
private Engine $engine;
public function __construct() {
$this->engine = new Engine();
}
public function start(): string {
return $this->engine->start();
}
}Класс Car не наследуется от Engine — он владеет им. Это обеспечивает слабую связанность классов и позволяет избежать жестких иерархий наследования.
Предпочитайте композицию наследованию: Наследование хорошо работает для подлинных иерархий типов (Dog — это Animal), но композиция проявляет себя лучше всего при сборке функциональности из независимых частей. Вы можете комбинировать несколько объектов, заменять их во время выполнения и тестировать компоненты изолированно.
Попробуйте сами
<?php
require_once 'Processor.php';
require_once 'Memory.php';
class Computer
{
// TODO: Определите приватные свойства для хранения объектов Processor и Memory
// TODO: Конструктор должен принимать модель процессора, количество ядер и объем памяти
// и создавать объекты компонентов внутри
public function __construct()
{
// TODO: Создайте объекты Processor и Memory и сохраните их
}
// TODO: Реализуйте метод describe(), который возвращает
// "Computer with [характеристики процессора] и [объем памяти]"
public function describe(): string
{
// TODO: Делегируйте выполнение объектам компонентов и верните отформатированную строку
}
}
// Чтение входных данных
$processorModel = trim(fgets(STDIN));
$cores = intval(fgets(STDIN));
$memorySize = intval(fgets(STDIN));
// TODO: Создайте Computer с заданными характеристиками и выведите результат describe()
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово $thisМетодыСвойстваКонструктор (__construct)Деструктор (__destruct)Итоги — Простой калькулятор4Наследование
Основы наследованияКлючевое слово parent::Переопределение методовКлючевое слово finalАбстрактные классыИтоги — Иерархия сотрудников7Инкапсуляция
Public, Protected, PrivateПодробный разбор модификаторов доступаГеттеры и сеттерыСокрытие данныхConstructor Promotion (8.0)Итоги — Система учета студентов10Продвинутые концепции ООП
Композиция против наследованияВнедрение зависимостейАнонимные классыПеречисления (PHP 8.1)Файберы (PHP 8.1)Глубокое погружение в клонирование объектовГенераторы и итераторы13Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий2Пространства имен и автозагрузка
Введение в пространства именКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerИтоги: структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс против абстрактного классаType Hinting с интерфейсамиИтоги — Калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeИтоги — Кастомная коллекция11Система типов и обработка ошибок
Объявление типовNullable-типыТипы объединения и пересеченияКлассы исключенийИерархия пользовательских исключенийTry, Catch, FinallyИтоги — Валидатор форм14Проект: Управление библиотекой
Обзор проектаКлассы Book и User3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищенные свойстваСвойства Readonly (PHP 8.1)Итоги — Менеджер банковского счета6Полиморфизм
Снова о переопределении методовПолиморфизм через интерфейсыType Hinting и Union TypesПозднее статическое связываниеИтоги — Payment Processor9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy