Интерфейс против абстрактного класса
Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 30 из 91.
Теперь, когда вы изучили и интерфейсы, и абстрактные классы, давайте проясним, когда использовать каждый из них. Хотя они могут казаться похожими, они служат разным целям и обладают различными возможностями.
| Особенность | Интерфейс | Абстрактный класс |
|---|---|---|
| Методы | Только сигнатуры методов | Как абстрактные, так и конкретные методы |
| Свойства | Только константы | Может иметь свойства |
| Множественное наследование | Класс может реализовывать несколько | Класс может расширять только один |
| Конструктор | Не допускается | Может иметь конструктор |
Используйте интерфейс, когда вы хотите определить контракт, который могут выполнять несвязанные классы. Например, и Printer, и Logger могут реализовывать интерфейс Writable, даже если у них нет общего предка.
Используйте абстрактный класс, когда у вас есть связанные классы, имеющие общий код. Если вашим классам Dog и Cat обоим требуется одинаковая реализация метода eat(), абстрактный класс Animal позволит вам написать этот код один раз.
Вы также можете комбинировать оба подхода. Класс может расширять абстрактный класс, одновременно реализуя несколько интерфейсов:
<?php
abstract class Animal {
protected $name;
public function eat() {
return $this->name . " is eating";
}
}
interface Swimmable {
public function swim();
}
class Duck extends Animal implements Swimmable {
public function __construct($name) {
$this->name = $name;
}
public function swim() {
return $this->name . " is swimming";
}
}
Ключевой момент: Выбирайте интерфейсы для определения возможностей в несвязанных классах. Выбирайте абстрактные классы, когда вам нужно разделить код между связанными классами.
Задание
ЛегкоДавайте создадим систему транспортных средств, которая продемонстрирует, когда использовать абстрактный класс, а когда интерфейс — и как эффективно комбинировать оба подхода.
Вы создадите систему, в которой транспортные средства разделяют общие свойства через наследование, но также могут получать дополнительные возможности через интерфейсы. Это отражает реальные проектные решения: автомобиль «является» транспортным средством (наследование), но он также «обладает способностью» заправляться (интерфейс).
Организуйте свой код в четырех файлах:
Refuelable.php— Определите интерфейсRefuelableс одной сигнатурой метода:refuel($amount). Эта возможность может быть применена к любому классу, которому требуется заправка, независимо от его иерархии наследования.Vehicle.php— Определите абстрактный классVehicle, который предоставляет общую функциональность для всех транспортных средств. Он должен иметь защищенное свойство$brandи защищенное свойство$fuelLevel(начинающееся с 0). Конструктор принимает название бренда. Включите конкретный методgetBrand(), который возвращает бренд, конкретный методgetFuelLevel(), который возвращает текущий уровень топлива, и абстрактный методdrive(), который должны реализовать дочерние классы.Car.php— Создайте классCar, который расширяетVehicleи реализуетRefuelable. Подключите оба необходимых файла в начале. Конструктор принимает бренд и передает его в родительский конструктор. Реализуйтеdrive(), чтобы он возвращал"[brand] car is driving". Реализуйтеrefuel($amount), чтобы добавить количество к уровню топлива и вернуть"[brand] refueled to [fuelLevel] liters".main.php— Подключите файл Car. Вы получите два входных значения: название бренда и количество топлива (преобразуйте в целое число). Создайте объектCarс указанным брендом. Выведите результат выполненияdrive()на первой строке, затем выведите результат выполненияrefuel()с указанным количеством топлива на второй строке.
Эта задача иллюстрирует ключевое различие: абстрактный класс Vehicle разделяет код и устанавливает отношение «является», в то время как интерфейс Refuelable добавляет возможность, которая может применяться к другим несвязанным классам (например, к генератору или лодке), не требуя, чтобы они были транспортными средствами.
Шпаргалка
Интерфейсы и абстрактные классы служат разным целям:
| Особенность | Интерфейс | Абстрактный класс |
|---|---|---|
| Методы | Только сигнатуры методов | Как абстрактные, так и конкретные методы |
| Свойства | Только константы | Может иметь свойства |
| Множественное наследование | Класс может реализовывать несколько | Класс может расширять только один |
| Конструктор | Не допускается | Может иметь конструктор |
Используйте интерфейс для определения контракта для несвязанных классов. Используйте абстрактный класс для совместного использования общего кода между связанными классами.
Класс может расширять абстрактный класс, одновременно реализуя несколько интерфейсов:
<?php
abstract class Animal {
protected $name;
public function eat() {
return $this->name . " is eating";
}
}
interface Swimmable {
public function swim();
}
class Duck extends Animal implements Swimmable {
public function __construct($name) {
$this->name = $name;
}
public function swim() {
return $this->name . " is swimming";
}
}
Попробуйте сами
<?php
require_once 'Car.php';
// Чтение входных данных
$brand = trim(fgets(STDIN));
$fuelAmount = intval(trim(fgets(STDIN)));
// TODO: Создать объект Car с указанной маркой
// TODO: Вывести результат drive() на первой строке
// TODO: Вывести результат refuel() с количеством топлива на второй строке
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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