Интерфейсы в ООП на PHP
Как вы уже знаете, абстрактные классы представляют собой набор методов для своих потомков. Часть этих методов может быть реализована в самом классе, а часть методов может быть объявлена абстрактными и требовать реализации в дочерних классах.
Представим себе ситуацию, когда ваш абстрактный класс представляет собой только набор абстрактных публичных методов, не добавляя методы с реализацией.
Фактически ваш родительский класс описывает потомков, то есть набор их публичных методов, обязательных для реализации.
Зачем нам такое нужно: чтобы при программировании совершать меньше ошибок — описав все необходимые методы в классе-родителе, мы можем быть уверенны в том, что все потомки их действительно реализуют.
Когда это поможет: пусть мы создадим наш класс-родитель и несколько потомков к нему. Если потом через некоторое время, например, через месяц, мы решим создать еще одного потомка, наверняка мы уже забудем детали нашего кода и вполне можем забыть написать реализацию какого-нибудь метода в новом потомке. Однако сам PHP не позволит потерять метод — и просто выдаст ошибку.
То же самое касается другого программиста, работающего с вашим проектом. Пусть код класса-родителя писали вы, а затем ваш коллега решил создать еще одного потомка. У вашего коллеги также не получится потерять парочку методов.
Есть, однако, проблема: фактически мы сделали наш класс-родитель для того, чтобы писать в нем абстрактные публичные методы, но мы сами или наш коллега имеем возможность случайно добавить в этот класс не публичный метод или не абстрактный.
Пусть мы хотим физически запретить делать в родителе иные методы, кроме абстрактных публичных. В PHP для этого вместо абстрактных классов можно использовать .
Интерфейсы представляют собой классы, у которых все методы являются публичными и не имеющими реализации. Код методов должны реализовывать классы-потомки интерфейсов.
Интерфейсы объявляются так же, как и обычные классы, но используя ключевое слово interface вместо слова class .
Для наследования от интерфейсов используется немного другая терминология: говорят, что классы не наследуют от интерфейсов, а их. Соответственно вместо слова extends следует использовать ключевое слово implements .
Нельзя создать объект интерфейса. Все методы интерфейса должны быть объявлены как public и не должны иметь реализации. У интерфейса могут быть только методы, но не свойства. Нельзя также сделать интерфейс и класс с одним и тем же названием.
Попробуем на практике
Давайте попробуем на практике. Решим задачу на фигуры из предыдущего рока, но уже используя интерфейсы, а не абстрактные классы.
Итак, теперь у нас дан интерфейс Figure :
Давайте напишем класс Quadrate , который будет реализовывать методы этого интерфейса:
a = $a; > public function getSquare() < return $this->a * $this->a; > public function getPerimeter() < return 4 * $this->a; > > ?>?php>
Как это работает: если забыть реализовать какой-нибудь метод, описанный в интерфейсе, PHP выдаст нам фатальную ошибку. Давайте реализуем также класс Rectangle :
Сделайте класс Disk ( круг ), реализующий интерфейс Figure .
Замечание
Как уже было написано выше, не может быть интерфейса и класса с одинаковым названием. Это создает некоторые проблемы с придумыванием названий. Например, мы хотим сделать класс User , реализующий интерфейс User . Как мы видим, у нас конфликт имен. Для его разрешения, нужно или класс назвать по-другому, или интерфейс.
Общепринято в таком случае название интерфейса начать с маленькой буквы i , чтобы показать, что это интерфейс, а не класс. То есть в нашем случае мы сделаем интерфейс iUser , а реализовывать его будет класс User . Такой подход мы иногда будем применять в следующих уроках.
Интерфейсы объектов
Интерфейсы объектов позволяют создавать код, который указывает, какие методы должен реализовать класс, без необходимости описывания их функционала.
Интерфейсы объявляются так же, как и обычные классы, но с использованием ключевого слова interface. Тела методов интерфейсов должны быть пустыми.
Все методы, определенные в интерфейсы должны быть публичными, что следует из самой природы интерфейса.
implements
Для реализации интерфейса используется оператор implements. Класс должен реализовать все методы, описанные в интерфейсе; иначе произойдет фатальная ошибка. При желании классы могут реализовывать более одного интерфейса за раз, реализуемые интерфейсы должны разделяться запятой.
Замечание:
Класс не может реализовать два интерфейса, содержащих одноименную функцию, так как это повлечет за собой неоднозначность.
Замечание:
Интерфейсы могут быть унаследованы друг от друга, так же как и классы, с помощью оператора extends.
Замечание:
Сигнатуры методов в классе, реализующем интерфейс, должны точно совпадать с сигнатурами, используемыми в интерфейсе, в противном случае будет вызвана фатальная ошибка.
Константы (Constants)
Интерфейсы могут содержать константы. Константы интерфейсов работают точно так же, как и константы классов, за исключением того, что они не могут быть перекрыты наследующим классом или интерфейсом.
Примеры
Пример #1 Пример интерфейса
// Объявим интерфейс ‘iTemplate’
interface iTemplate
public function setVariable ( $name , $var );
public function getHtml ( $template );
>
// Реализуем интерфейс
// Это сработает нормально
class Template implements iTemplate
private $vars = array();
public function setVariable ( $name , $var )
$this -> vars [ $name ] = $var ;
>
public function getHtml ( $template )
foreach( $this -> vars as $name => $value ) $template = str_replace ( » , $value , $template );
>
// Это не будет работать
// Fatal error: Class BadTemplate contains 1 abstract methods
// and must therefore be declared abstract (iTemplate::getHtml)
// (Фатальная ошибка: Класс BadTemplate содержит 1 абстрактный метод
// и поэтому должнен быть объявлен абстрактным (iTemplate::getHtml))
class BadTemplate implements iTemplate
private $vars = array();
public function setVariable ( $name , $var )
$this -> vars [ $name ] = $var ;
>
>
?>
Пример #2 Расширяемые интерфейсы
interface b extends a
public function baz ( Baz $baz );
>
// Это сработает
class c implements b
public function foo ()
>
public function baz ( Baz $baz )
>
>
// Это не сработает и выдаст фатальную ошибку
class d implements b
public function foo ()
>
Пример #3 Множественное наследование интерфейсов
interface b
public function bar ();
>
interface c extends a , b
public function baz ();
>
class d implements c
public function foo ()
>
public function bar ()
>
Пример #4 Интерфейсы с константами
interface a
const b = ‘Константа интерфейса’ ;
>
?php
// Выведет: Константа интерфейса
echo a :: b ;
// Вот это, однако, не будет работать, так как
// константы перекрывать нельзя.
class b implements a
const b = ‘Class constant’ ;
>
?>
Интерфейс, совместно с контролем типов, предоставляет отличный способ проверки того, что определенный объект содержит определенный набор методов. Смотрите также оператор instanceof и контроль типов.
Что такое интерфейс php
Интерфейс определяет абстрактный дизайн, которому должен соответствовать применяющий его класс. Интерфейс определяет методы без реализации. А класс затем применяет интерфейс и реализует эти методы. Применение интерфейса гарантирует, что класс имеет определенный функционал, описываемый интерфейсом.
Интерфейс определяется с помощью ключевого слова interface , за которым следует имя интерфейса и блок кода интерфейса в фигурных скобках:
interface Messenger <>
Здесь определен интерфейс Messenger . Внутри блока интерфейса в фигурных скобках определяются сигнатуры методов. Причем все эти методы могут быть только публичными, то есть с модификатором public , либо без модификатора доступа (что аналоично модификатору public):
interface Messenger
Интерфейсы могут содержать лишь сигнатуры методов — наазвания функций и список параметров в скобках, после которого идет точка с запятой. Так, в данном случае объявлен метод send() . Он не имеет реализации — конкретную реализацию определит класс, который реализует этот интерфейс.
Для реализации классом интерфейса применяется ключевое слово implements , после которого указывается имя применяемого интерфейса:
class EmailMessenger implements Messenger < function send() < echo "Отправка сообщения на e-mail"; >> $outlook = new EmailMessenger(); $outlook->send(); ?>
В данном случае класс EmailMessenger реализует интерфейс Messenger. Если класс применяет интерфейс, то он должен реализовать все методы этого интерфейса. Так, в данном случае класс EmailMessenger определяет метод send() с некоторой реализацией.
Интерфейсы также могут наследоваться от других интерфейсов:
interface EmailMessenger extends Messenger < >class SimpleEmailMessenger implements EmailMessenger < function send() < echo "Отправка сообщения на email."; >> $outlook = new SimpleEmailMessenger(); $outlook->send(); ?>
Когда нам могут понадобиться интерфейсы? Интерфейс — это контракт, который говорит, что класс обязательно реализует определенный функционал. И мы можем использовать это в своей программе. Например, определим следующий код:
function sendMessage(Messenger $messenger, $text) < $messenger->send($text); > class EmailMessenger implements Messenger < function send($message) < echo "Отправка сообщения на email: $message"; >> $outlook = new EmailMessenger(); sendMessage($outlook, "Hello World"); ?>
Для отправки сообщения здесь определена функция sendMessage() , которая в качестве первого параметра принимает объект мессандера, а в качестве второго параметра — отправляемый текст. Причем определение первого параметра говорит, что передаваемое этому параметру значение должно реализовать интерфейс Messenger. В самой функции мы знаем, что первый параметр — это объект, который обязательно реализует интерфейс Messenger, поэтому мы можем вызвать у него метод send() для отправки сообщения:
function sendMessage(Messenger $messenger, $text) < $messenger->send($text); >
Множественное применение интерфейсов
Класс может одновременно применять сразу несколько интерфейсов. В этом случае все интерфейсы перечисляются через запятую после слова implements . А класс должен реализовать методы всех применяемых интерфейсов:
interface Camera < function makeVideo(); function makePhoto(); >interface Messenger < function sendMessage($message); >class Mobile implements Camera, Messenger < function makeVideo()< echo "Запись видео";>function makePhoto() < echo "Съемка фото";>function sendMessage($message) > $iphone12 = new Mobile(); $iphone12->makePhoto();
PHP: Что такое Интерфейс
Что такое PHP-интерфейс? PHP интерфейс определяет контракт, который должен выполнить класс. Если PHP класс — это схема объектов, то интерфейс — это схема классов. Любой класс реализующий данный интерфейс, будет иметь одинаковое поведение с точки зрения того, что можно вызвать, как его можно вызвать и что будет возвращено.
В предыдущей статье мы говорили о PHP классах. Исходя из этого, сегодня мы поговорим об интерфейсах.
Основы интерфейсов в PHP
В качестве примера основ PHP интерфейса рассмотрим что-то издающее звуки . В реальном мире это может быть птица ( чирикает ), собака ( лает ), кошка ( мяукает ) или человек ( поёт ). Детали озвучивания специфичны для каждого типа, но каждый может издавать звуки.
Мы можем описать это следующим образом:
interface Vocalizer
public function vocalize(string $message): string;
>
То о чём мы говорили выше: полученную строку $message , vocalize() вернёт то, что слышно, как строку.
Теперь интерфейс ничего не делает сам по себе. Он действует как тип PHP. Значит вы можете указать его в качестве аргумента или даже вернуть что-то в этом типе из функции или метода.
Для создания чего-либо этого типа, нам нужна доступная реализация. Классы могут реализовывать интерфейс:
class Bird implements Vocalizer
public function vocalize(string $message): string
return sprintf('%s ', $message);
>
>
Допустим у нас есть следующая функция:
function prepareMessage(string $message, Vocalizer $vocalizer): string
return $vocalizer->vocalize($message);
>
Вышеупомянутая функция может быть вызвана с любым $vocalizer реализующим Vocalizer :
$chickadee = new Bird();
echo prepareMessage('a song', $chickadee); // "a song "
Наследование и подстановка
Короче говоря, интерфейсы дают возможность предоставлять функцию не требуя наследования классов. Это может быть полезно для адаптации существующих классов для работы в других контекстах.
В качестве примера предположим, что у нас есть класс Bird не реализующий Vocalizer , но у нас есть практически эквивалентная функциональность через метод tweet() :
class Bird
public function tweet(string $message): string
return sprintf('%s ', $message);
>
>
На данный момент мы можем сделать одну из двух вещей, чтобы сделать этот класс Vocalizer , сохранив при этом всю существующую функциональность.
Во-первых, мы могли бы обновить класс, чтобы напрямую реализовать интерфейс Vocalizer :
class Bird implements Vocalizer
public function vocalize(string $message): string
return $this->tweet($message);
>
public function tweet(string $message): string
return sprintf('%s ', $message);
>
>
В качестве альтернативы мы могли бы создать расширение Bird , реализующее Vocalizer :
class VocalizingBird extends Bird implements Vocalizer
public function vocalize(string $message): string
return $this->tweet($message);
>
>
Поскольку он является расширением Bird , он соответствует типу Bird для подсказок типа; поскольку он так же реализует Vocalizer , он также соответствует этому типу. Вы можете выполнить вышеуказанное с помощью реализации анонимного класса:
$bird = new class extends Bird implements Vocalizer
public function vocalize(string $message): string
return $this->tweet($message);
>
>;
Это приводит нас к ключевому обоснованию использования интерфейсов: возможности замены одного типа другим.
Пять принципов объектно-ориентированного программирования
При реализации объектно-ориентированного программирования существует набор из пяти основных принципов проектирования рекомендуемых для создания гибких, удобных в сопровождении архитектур, известных как принципы SOLID:
- Single-responsibility principle — Принцип единой ответственности
- Open-closed principle — Принцип Открытости/Закрытости
- Liskov substitution principle — Принцип подстановки Барбары Лисков
- Interface segregation principle — Принцип разделения интерфейса
- Dependency inversion principle — Принцип инверсии зависимости
Принцип инверсии зависимости
На что я хочу обратить внимание, так это на принцип инверсии зависимости.
Принцип инверсии зависимости гласит, что мы должны зависеть от абстракций, а не от реализации. Что это значит?
Если нас беспокоит только озвучивание, нам не нужно беспокоиться о том, есть ли у нас Bird , Human или Animal . Мы должны беспокоиться о том, есть ли у нас что-то способное издавать звуки.
Интерфейсы позволяют нам определять эти возможности, а затем позволяют коду подсказывать тип этих возможностей, а не более конкретный тип. Это, в свою очередь, позволяет нам заменять разные типы, когда они выполняют контракт, определённым интерфейсом.
Распространённая ошибка при объектно-ориентированном программировании
Одной из распространённых ошибок при начале объектно-ориентированного программирования является создание строгого дерева наследования: базовый класс, затем подтипы этого базового класса, затем реализация этих подтипов и так далее.
Это может привести к созданию класса, который технически имеет десяток или более различных вариантов поведения, но используется только для одного из них. Разделив это поведение на разные интерфейсы, мы можем создавать классы реализующие только определённое поведение, и использовать их везде, где это поведение необходимо.
Что можно определить в интерфейсе
Теперь, когда у нас есть общее представление, что такое интерфейс, зачем его использовать и как его реализовать, Что мы можем определить в интерфейсе?
Интерфейсы в PHP ограничены:
- Публичными методами.
- Публичными константами.
В предыдущей статье о PHP классах мы отметили, что видимость — более сложная тема. Это всё ещё так, но мы можем осветить некоторые основы. В двух словах, видимость помогает детализировать, что и где может использовать функциональность. К чему-то, что имеет публичную видимость, можно получить доступ как из методов класса, так и из экземпляров. Что подразумевается под последним? В следующем:
$chickadee = new Bird();
$chickadee — это экземпляр.
Отступление: Константы класса
В статье о классах мы не рассматривали константы классов. Константа похожа на обычную PHP константу в том смысле, что она детализирует значение, которое остаётся постоянным. Это значение нельзя переназначить позже. Для определения константы класса используется ключевое слово const в объявлении класса и, необязательно, оператор видимости (как у свойств и методов, видимость по умолчанию public ):
class Bird
public const TAXONOMY_CLASS = 'aves';
>
Ссылаясь на константу используют имя класса и имя константы разделённые двоеточием : :
$taxonomy = Bird::TAXONOMY_CLASS;
Константа класса может быть любого скалярного типа или массивом, если ни один из членов массива не является объектом. При необходимости они могут даже ссылаться на другие константы.
По соглашению имена констант обычно пишут ЗАГЛАВНЫМИ БУКВАМИ с использованием подчёркивания _ в качестве разделителя слов.
Константы определённые в интерфейсе наследуются всеми реализациями.
При определении метода в интерфейсе вы опускаете тело и его фигурные скобки и вместо этого заканчиваете объявление точкой с запятой ; ; вы определяете только сигнатуру. Они указывают, что реализация должна определить, чтобы быть допустимой.
Мы уже видели определение метода ранее, когда определяли метод vocalize() в интерфейсе Vocalizer :
public function vocalize(string $message): string;
Таким образом, любая реализация должна определять этот метод и быть совместимой. Они могут добавлять дополнительные аргументы, но только если эти аргументы являются необязательными. Только так они могут отличаться.
Возможность реализации множественного наследования
Одной из возможностей, предлагаемых некоторыми языками, является множественное наследование. Эта возможность позволяет объекту расширять несколько других типов и, таким образом, выполнять их все. Например, класс Horse может расширять как класс Animal , так и класс Vehicle .
PHP не предлагает множественного наследования, по крайней мере, напрямую. Однако он обеспечивает возможность реализации нескольких интерфейсов. Это может быть полезно, когда вы хотите описать подмножество функций, предоставляемых конкретным классом в данном контексте.
Пример реализации нескольких интерфейсов
Например, пакет laminas/laminas-cache определяет множество интерфейсов, описывающих возможности адаптера хранилища, включая FlushableInterface , OptimizableInterface , ClearByPrefixInterface , TaggableInterface , и т.д. Отдельные адаптеры — это всё адаптеры хранения, но они могут указывать, что у них есть другие возможности, реализуя эти интерфейсы.
Для реализации нескольких интерфейсов, вы предоставляете список интерфейсов, разделённых запятыми, после ключевого слова implements при объявлении вашего класса:
class MyCustomStorageAdapter extends AbstractAdapter implements
ClearByStorageInterface,
FlushableInterface,
OptimizableInterface,
TaggableInterface
// . . .
>
Экземпляр этого класса будет выполнять подсказки типов для каждого из этих интерфейсов.
Именование интерфейсов PHP
Как вы должны называть свой интерфейс? Вообще назовите его на основе поведения, которое интерфейс описывает. В нашем примере мы определяли что-то издающее звуки , поэтому мы назвали интерфейс Vocalizer .
Это может быть трудно описать, особенно если вы извлекаете интерфейс из класса, который вы ранее определили, где логическое имя уже является именем существующего класса (например, вы можете захотеть определить интерфейс Logger , но класс Logger уже существует).
Иногда трудности с присвоением имени возникают из-за того, что в команде разработчиков разный опят или страны (например, у некоторых членов команды может быть разный родной язык). Таким образом, многие проекты используют суффикс interface для упрощения решения об именовании, а также указать, какие файлы классов в проекте являются контрактами, а какие реализациями.
Вы и ваша команда должны решить, какой подход наиболее подходящий для вас!
Заключение
PHP интерфейсы предоставляют повторно используемы типы, подробно описывающие конкретное поведение, которое может использовать ваше приложение. В частности, они позволяют создавать классы с известным ожидаемым поведением, которое соответствует вашей собственной архитектуре приложения. Интерфейсы PHP также позволяют заменять различные реализации, независимо от обычного наследования классов, что даёт больше гибкости в структуре приложения.