Link Search Menu Expand Document
30 Января 2021 г.

Интерфейсы и абстрактные классы в php

Содержание
  1. Что такое интерфейс
  2. Реализация интерфейса
  3. Наследование интерфейсов
  4. Где нужны интерфейсы
  5. Что такое абстрактный класс
  6. Особенности абстрактного класса
  7. Итог

Что такое интерфейс

Интерфейс — это конструкция языка содержащая сигнатуры публичных методов без их реализации.

Интерфейс, как и класс пишут в отдельном файле.

Для объявления интерфейса используется ключевое слово interface. Так же принято его указывать в названии интерфейса.

Примеры названий интерфейса:

  • ContainerApplicationInterface
  • StatementInterface
  • NodeTraverserInterface
  • WrappableOutputFormatterInterface
<?php

// Пример 1

interface BuildNodeInterface
{
    public function getNode($id) : Node;
}

Реализация интерфейса

Интерфейс может содержать только публичные методы и публичные константы.

<?php

// Пример 2

interface TestInterface
{
    public const CONSTANT = 1; // использование снаружи BuildNodeInterface::CONSTANT
    public function __construct();
    public function getMethodOne(int $id);
    public function getMethodTwo(string $name);
}

С точки зрения прикладного кода недостаточно просто создать интерфейс. Отдельно, в нем нет смысла. Интерфейс необходимо реализовать с помощью ключевого слова implements.

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

Константу нельзя переопределить в реализующих классах.

В примере 3 мы реализуем интерфейс TestInterface

<?php

// Пример 3 . Интерфейс TestInterface объявлен в примере 2

class RealizableClass implements TestInterface {

    public function __construct()
    {
    }

    public function getMethodOne(int $id)
    {
        // TODO: Implement getMethodOne() method.
    }

    public function getMethodTwo(string $name)
    {
        // TODO: Implement getMethodTwo() method.
    }
}

Наследование интерфейсов

Интерфейс может наследоваться от нескольких других интерфейсов. В добавок к тому класс может реализовывать несколько интерфейсов.

Например объявим три интерфейса. Четвертый FourInterface будет наследовать три.

А класс Realizable будет реализовывать интерфейсы FourInterface и \Countable.

Интерфейс \Countable встроенный интерфейс в сам язык и нужен для подсчета элементов.


// Пример 4

interface OneInterface
{
    public function One(array $array = []) : array;
}

interface TwoInterface
{
    public function Two(int $integer) : int;
}

interface ThreeInterface
{
    public function Three(string $integer) : int;
}

interface FourInterface extends OneInterface,TwoInterface,ThreeInterface
{
    // Здесь мы дополняем сигнатуру метода One
    public function One(array $array = [], int $integer = 1) : array;
}

class Realizable implements FourInterface, \Countable
{

    public function Two(int $integer): int
    {
        // TODO: Implement Two() method.
    }

    public function Three(string $integer): int
    {
        // TODO: Implement Three() method.
    }

    public function One(array $array = [], int $integer = 1): array
    {
        // TODO: Implement One() method.
    }

    public function count(): int
    {
        // TODO: Implement count() method.
    }
}

Возможно так же использовать интерфейс в сигнатурах функций и методов, как показано в примере 5.

<?php

// Пример 5

function test(FourInterface $four): string
{
    return 'test';
}

$obj = new Realizable(); // класс этого объекта должен реализовывать FourInterface

echo test($obj); // test

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

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

Где нужны интерфейсы

К примеру для реализации стандарта PSR-7 нужно реализовать интерфейсы:

  • ServerRequestInterface
  • ResponseInterface

Реализацию данных интерфейсов можно увидеть во многих фреймворках, например в slim:

Мы можем без проблем менять одну библиотеку на другую совместимую с PSR-7.

Интерфейс лучше добавлять тогда, когда он нужен.

Задача. Нужно преобразовать строку к нужным переводам строк в разных операционных системах.

Здесь может помочь интерфейс.

<?php

// Пример 6

interface LineInterface
{
    public function createWrite(string $line) : string;
}

class WinWrite implements LineInterface
{

    public function createWrite(string $line): string
    {
        return $line . "\r\n";
    }
}

class UnixWrite implements LineInterface
{

    public function createWrite(string $line): string
    {
        return $line . "\n";
    }
}

Теперь при добавлении другой операционной системой достаточно создать новый класс и реализовать там интерфейс LineInterface

Для решения данной задачи лучше подойдет константа PHP_EOL.

Когда нужно использовать интерфейс — это уже зависит от конкретной ситуации и понимание этого момента придёт с опытом и практикой.

Что такое абстрактный класс

Теперь попытаемся понять для чего нужен абстрактный класс в php.

Напишем компонент логирования.


// Пример 7

interface LogInterface
{
    public function __construct($options); // Настроить объект значениями
    public function get($key); // Получить значение лога
    public function set($key, $value); // Положить значение в лог
}

abstract class Log implements LogInterface
{
    protected array $options;

    public function __construct($options)
    {
        $this->options = $options;
        $this->write();
    }
    // Общая реализация для всех потомков
    abstract protected function write();
}

// Конкретные реализации

class FileLog extends Log
{

    public function get($key)
    {
        // TODO: Implement get() method.
    }

    public function set($key, $value)
    {
        // TODO: Implement set() method.
    }

    protected function write()
    {
        // TODO: Implement write() method.
    }
}

class DBLog extends Log
{

    public function get($key)
    {
        // TODO: Implement get() method.
    }

    public function set($key, $value)
    {
        // TODO: Implement set() method.
    }

    protected function write()
    {
        // TODO: Implement write() method.
    }
}

Имеем LogInterface в котором определен конструктор и методы get и set, которые работают с коллекцией логов. Логи могут храниться в разных хранилищах, и их нужно куда-то записывать. Создадим вспомогательный класс Log и сделаем его абстрактным.

Добавим конструктор, который будет наделять наш объект специфичными для класса потомка опциями, Так же вызовем метод write.

От абстрактного класса нельзя создать объект, его методы нужно переопределить в классе наследнике

Метод write является общим, его и нужно переопределять в классе наследнике.

Теперь куда бы мы не писали логи, для этого достаточно создать класс и переопределить в нем методы get, set, write.

Итак, типичное использование абстрактных классов это уменьшение дублирования кода, при появлении общих методов. Просто выносим общую логику в абстрактный метод.

Абстрактный класс может полностью не реализовывать все методы интерфейса, тогда определения методов из интерфейса становятся абстрактными методами в этом классе, и должны быть переопределены в классе наследнике.

Особенности абстрактного класса

  • Технически абстрактный класс может не содержать абстрактных методов.
  • В отличие от интерфейса в абстрактном классе для части методов можно написать реализацию.
  • Никто не мешает использовать абстрактные классы вместо интерфейсов, все зависит от задачи.
  • Абстрактный класс не имеет никакого отношения к ООП. Это способ распространения кода в вашей иерархии кода.

Итог

Итак, интерфейс — это контракт, который содержит сигнатуры методов без их реализации.

Абстрактный класс содержит “очень” общую логику для всех классов потомков, что сокращает дублирование кода.


Возник вопрос или предложение пиши на почту alexsey_89@bk.ru или в Телеграмм канал

Дата публикации: 30 Января 2021 г.