Menu
Coddy logo textTech

Интерфейс против абстрактного класса

Часть раздела Объектно-ориентированное программирование путешествия по 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";
    }
}

Ключевой момент: Выбирайте интерфейсы для определения возможностей в несвязанных классах. Выбирайте абстрактные классы, когда вам нужно разделить код между связанными классами.

challenge icon

Задание

Легко

Давайте создадим систему транспортных средств, которая продемонстрирует, когда использовать абстрактный класс, а когда интерфейс — и как эффективно комбинировать оба подхода.

Вы создадите систему, в которой транспортные средства разделяют общие свойства через наследование, но также могут получать дополнительные возможности через интерфейсы. Это отражает реальные проектные решения: автомобиль «является» транспортным средством (наследование), но он также «обладает способностью» заправляться (интерфейс).

Организуйте свой код в четырех файлах:

  • 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() с количеством топлива на второй строке
?>
quiz iconПроверьте себя

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

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