Что обеспечивает interface и для чего он нужен Перевод
Создание и внедрение интерфейсов в наш код — это важно. Это помогает с подменой компонентов, облегчает тестирование, отделяет «что» от «как».
Но просто влепить интерфейс к классу и забыть — этого недостаточно.
Мы также должны подумать о том, на что мы «натянем» этот интерфейс.
Пример
Допустим, мы создаем систему очередей для чтения RSS канала. И нам нужно ставить в очередь URL-адреса. В зависимости от наших потребностей, в качестве механизма очередей мы можем использовать что-то вроде в RabbitMq или базы данных.
Мы ещё не решили точно, но в любом случае мы начнем с интерфейса для этой воображаемой очереди:
declare(strict_types=1); namespace Example\Infrastructure\Queue; use Example\Domain\Rss\FeedUrl; interface FeedUrlQueue
Имея такой миленький интерфейс, мы можем разработать и покрыть тестами ту часть кода, которая будет использовать реализацию этого интерфейса.
Через некоторое время мы решили, что для начала мы будем использовать базу данных в качестве механизма очередей. Соответственно, мы создаем реализацию FeedUrlQueue интерфейса:
declare(strict_types=1); namespace Example\Infrastructure\Storage\Database; use Example\Domain\Rss\FeedUrl; class FeedUrlTable extends AbstractTable implements FeedUrlQueue < public function add(FeedUrl $feedUrl) < $qb = $this->getQueryBuilder(); $query = $qb->insert('feed_urls') ->values( [ 'url' => '?', ] ) ->setParameter(0, (string) $feedUrl); $query->execute(); > >
Отлично! У нас есть интерфейс, конкретная реализация, есть возможность писать новые реализации и подменять ими текущую, как говорится, малой кровью.
А действительно ли она хорошо сделана?
Конечно, и я повторюсь: у нас есть интерфейс, конкретная реализация, есть возможность писать новые реализации и легко подменять ими старую.
Что-то тут нечисто
Для меня здесь есть три вещи, которые выбиваются, говоря мне, что что-то не так с этим кодом.
Во-первых, класс, который представляет собой Table , также является FeedUrlQueue . Тут действительно не должно быть двух вещей одновременно. Он либо должен быть очередью, либо таблицей и, безусловно, не тем и другим одновременно.
Во-вторых, класс, чья единственная ответственность должна быть сохранением URL-а в базе, – независимо от того, откуда этот URL приходит, – теперь ограничен сохранением URL-а RSS канала, который приходит из очереди. Ок, возможно (а может нет) это то ограничение, которое мы и хотим применить.
И в-третьих, он также несет ответственность за решение как преобразовать доменный объект FeedUrl в строку, которая можно сохранить в базе. Есть ли у него магический метод __toString , с помощью которого мы можем преобразовать его в строку? Или, может, это устаревший код с одним из тех методов toString() , которые нужно вызвать самим? Мы не узнаем, не взглянув.
Убиваем трех зайцев разом
Лучше было бы реализовать что-то вроде DatabaseFeedUrlQueue , который реализует FeedUrlQueue и использует FeedUrlTable :
declare(strict_types=1); namespace Example\Infrastructure\Queue; use Example\Domain\Rss\FeedUrl; class DatabaseFeedUrlQueue implements FeedUrlQueue < protected $table; public function __construct(FeedUrlTable $table) < $this->table = $table; > public function add(FeedUrl $feedUrl) < $payload = [ 'url' =>(string) $feedUrl ]; $this->table->save($payload); > >
а FeedUrlTable станет чем-то вроде этого:
declare(strict_types=1); namespace Example\Infrastructure\Storage\Database; class FeedUrlTable extends AbstractTable < public function save(array $payload) < $qb = $this->getQueryBuilder(); $query = $qb->insert('feed_urls') ->values( [ 'url' => '?', ] ) ->setParameter(0, $payload['url']); $query->execute(); > >
С помощью такого рефакторинга кода, как этот, мы в значительной степени исправили все три проблемы сразу:
- DatabaseFeedUrlQueue — это FeedUrlQueue , а FeedUrlTable теперь не является двумя сущностями одновременно;
- есть четкое разделение обязанностей: DatabaseFeedUrlQueue отвечает за создание хранимых данных, а FeedUrlTable обязан сохранять их;
- слой хранения ничего не знает о наших доменных объектах и как их использовать.
Да, теперь у нас есть еще один класс, который нужно поддерживать, но в целом поддержка, я считаю, уменьшается, т. к. сейчас гораздо прозрачнее назначение каждого класса.
Разница между абстрактным классом и интерфейсом в PHP
Сравнение абстрактного класса и интерфейса в PHP. В чем разница между абстрактным классом и интерфейсом в PHP? Это один из самых распространненых вопросов на собеседованиях.
В этом уроке вы узнаете некоторые важные различия между абстрактным классом и интерфейсом.
Интерфейс в PHP
Интерфейс — это соглашение, контракт или договор. Когда класс реализует интерфейс, это означает, что он содержит публичные методы интерфейса и их реализации.
Когда мы создаем интерфейс в приложении, мы определяем контракт на то, что наш класс может делать, ничего не говоря о том, как этот класс будет это делать.
// Defined cacheInterface interface cacheInterface < public function get($key); public function set($key,$value); >/* * A Memcache class implements cacheInterface */ class Memcache implements cacheInterface < public function get($key)< // method body >public function set($key,$value) < // method body >>
В приведенном выше примере создается cacheInterface , содержащий публичные методы get() и set() . Любой класс, который реализует этот интерфейс, должен определить методы get() и set() .
В этом примере класс Memcache реализует cacheInterface . Это означает, что он придерживается контракта и определяет тело методов get() и set() . Аналогично, если какой-либо другой класс кэширования реализует этот интерфейс, он определит методы get() и set() .
Абстрактный класс в PHP
Абстрактный класс используется для определения базового каркаса или проекта дочерних классов. Класс, который расширяет абстрактный класс, должен определять некоторые или все его абстрактные методы.
Экземпляры абстрактных классов не могут быть созданы напрямую.
abstract class car < abstract public function modelType(); public function wheelCount() < echo "I have four wheels"; >> class HondaCity extends car < public function modelType()< echo "HondaCity"; >> $data = new HondaCity; $data->modelType(); $data->wheelCount();
Сравнение абстрактного класса и интерфейса в PHP
Интерфейс | Абстрактный класс |
---|---|
Поддерживаем множественное наследование | Не поддерживаем множественное наследование |
Не содержит data members | Может содержать data members |
Не содержит конструктор | Может содержать конструктор |
Содержит только объявление методов (сигнатуры методов) | Может содержать как сигнатуры методов так и их реализации |
Не может иметь модификаторов доступа — все методы по умолчанию публичные | Может иметь модификаторы доступа |
Методы не могут быть статическими | Только методы, содержащие реализацию, могут быть объявлены статическими |
1. Абстрактный класс действует как образец для классов, которые его наследуют. Дочерний класс, который наследует абстрактный класс, должен реализовывать методы, объявленые абстрактными в родительском классе. Абстрактный класс создается только для как элемент иерархии наследования. Это означает, что вы не можете создать объект абстрактного класса.
Интерфейс — это контракт. Он содержит только сигнатуры методов (без тела метода). Класс, который реализует интерфейс, должен реализовывать эти методы.
2. Класс может расширять только один абстрактный класс, тогда как класс может реализовывать несколько интерфейсов.
interface A <> interface B <> interface C <> //A class can implements multiple interfaces class Base implements A, B, C <>
3. В абстрактном классе мы можем определить переменные экземпляра и конкретные (неабстрактные) методы.
Но в интерфейсе все методы абстрактны. Мы не можем определить переменные экземпляра, но мы можем определить константы в интерфейсе.
4. Абстрактный класс хорош, когда есть некоторые общие черты, которые должны быть общими для всех объектов.
Используйте интерфейс, когда все функции должны быть реализованы по-разному для разных объектов. Все методы cacheInterface будут реализованы по-разному для всех классов.
Например — класс Memcache определяет тело метода get() и set() , но их реализация будет отличается от класса Redis.
Интерфейсы и трейты в PHP
Интерфейсы (interface) — это класс, в котором все методы являются абстрактными (abstract) и открытыми (public). В интерфейсе ООП PHP можно задать только имена методов и их параметры, а реализованы они обязаны быть в расширяемых ими классах.
Для реализации интерфейса мы используем ключевое слово implements. Класс может реализовать более одного интерфейса, которые разделяются запятыми. Как и классы, интерфейс может содержать константы, которые запрещено переопределять.
Трейты в ООП PHP
Трейты (trait) — это механизм обеспечения повторного использования кода в PHP с учетом одиночного наследования. Трейт отчасти реализует множественное наследование, позволяя разработчику повторно использовать наборы методов свободно, в нескольких независимых классах.
Трейт похож на класс, но создать экземпляр трейта невозможно. Он предназначен для группирования функционала, который потом используется в разных классах с помощью директивы use. Нужные для использования в классе трейты можно указать через запятую.
interface NameA < const CON=5*10;//50 public function getA(); public function setA($a); > interface NameB < public function getB(); public function setB($b); >//Объединяем NameA и NameB в Name interface Name extends NameA, NameB <> //Трейт - похож на класс trait My1 < public function summa($a,$b):float< return $a+$b; >> trait My2 < public function multi($a,$b):float< return $a*$b; >> //class Gena implements NameA, NameB class Gena implements Name < use My1, My2;//Подключаем Трейты (множественное наследование) private $a=1; private $b=2; public function getA()< return $this->a; > public function getB()< return $this->b; > public function setA($a)< $this->a=$a; > public function setB($b)< $this->b=$b; > > $objG=new Gena; //Содаём обект класса Gena $objG->setA(10); //Меняем значение $a $objG->setB(20); //Меняем значение $b echo $objG->getA()."
\n"; //10 - новое значение echo $objG->getB()."
\n"; //20 - новое значение echo $objG->summa(30,40)."
\n";//70 (30+40) echo $objG->multi(30,40)."
\n";//1200 (30*40) echo Gena::CON."
\n"; //50 - константа ?>
Интерфейсы и абстрактные классы в php
Интерфейс — это конструкция языка содержащая сигнатуры публичных методов без их реализации.
Интерфейс, как и класс пишут в отдельном файле.
Для объявления интерфейса используется ключевое слово interface . Так же принято его указывать в названии интерфейса.
Примеры названий интерфейса:
- ContainerApplicationInterface
- StatementInterface
- NodeTraverserInterface
- WrappableOutputFormatterInterface
// Пример 1 interface BuildNodeInterface public function getNode($id) : Node; >
Реализация интерфейса
Интерфейс может содержать только публичные методы и публичные константы.
// Пример 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
// Пример 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.
// Пример 5 function test(FourInterface $four): string return 'test'; > $obj = new Realizable(); // класс этого объекта должен реализовывать FourInterface echo test($obj); // test
Запись интерфейса в параметрах функции, а не класса позволяет завязываться на интерфейс, что делает программу гибче.
С наследованием интерфейсов можно запутаться, главное придерживаться принципа интерфейс нужен для спецификации типа, то есть для того, что он может, а не наоборот.
Где нужны интерфейсы
К примеру для реализации стандарта PSR-7 нужно реализовать интерфейсы:
- ServerRequestInterface
- ResponseInterface
Реализацию данных интерфейсов можно увидеть во многих фреймворках, например в slim:
- ServerRequestInterface
- ResponseInterface
Мы можем без проблем менять одну библиотеку на другую совместимую с PSR-7.
Интерфейс лучше добавлять тогда, когда он нужен.
Задача. Нужно преобразовать строку к нужным переводам строк в разных операционных системах.
Здесь может помочь интерфейс.
// Пример 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 .
Итак, типичное использование абстрактных классов это уменьшение дублирования кода, при появлении общих методов. Просто выносим общую логику в абстрактный метод.
Абстрактный класс может полностью не реализовывать все методы интерфейса, тогда определения методов из интерфейса становятся абстрактными методами в этом классе, и должны быть переопределены в классе наследнике.
Особенности абстрактного класса
- Технически абстрактный класс может не содержать абстрактных методов.
- В отличие от интерфейса в абстрактном классе для части методов можно написать реализацию.
- Никто не мешает использовать абстрактные классы вместо интерфейсов, все зависит от задачи.
- Абстрактный класс не имеет никакого отношения к ООП. Это способ распространения кода в вашей иерархии кода.
Итог
Итак, интерфейс — это контракт, который содержит сигнатуры методов без их реализации.
Абстрактный класс содержит “очень” общую логику для всех классов потомков, что сокращает дублирование кода.