Menu
Coddy logo textTech

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

Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 30 из 91.

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

Особенность Интерфейс Абстрактный класс
Методы Только сигнатуры методов Как абстрактные, так и конкретные методы
Свойства Только константы Может иметь свойства
Множественное наследование Класс может реализовывать несколько Класс может расширять только один
Конструктор Может объявлять сигнатуру __construct (без тела) Может определять конструктор с телом

Используйте интерфейс, когда вы хотите определить контракт, который могут реализовать несвязанные классы. Например, и 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
require_once 'Car.php';

// Чтение входных данных
$brand = trim(fgets(STDIN));
$fuelAmount = intval(trim(fgets(STDIN)));

// TODO: Создать Car с маркой brand

// TODO: Вывести результат drive() в первой строке

// TODO: Вывести результат refuel() с количеством топлива во второй строке
?>
quiz iconПроверьте себя

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

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