Php что такое интерфейс
Перейти к содержимому

Php что такое интерфейс

  • автор:

Интерфейсы — PHP: Введение в ООП

Вместе с классами в PHP широко используется языковая конструкция «интерфейс». В этом уроке мы рассмотрим техническую сторону вопроса, а потом поговорим о смысле. Про последнее я сейчас могу сказать немного, потому что полноценный разговор про суть интерфейсов у нас пойдёт во время изучения полиморфизма в последующих курсах. А пока достаточно иметь общее представление об интерфейсах, так как без них не получится окунуться во фреймворки.

Интерфейс в PHP — конструкция языка, описывающая абстрактный тип данных (АТД). Напомню, что АТД определяет набор операций (функций), независимых от конкретной реализации типа (в нашем случае класса) для манипулирования его значениями. На практике интерфейсы содержат определения функций (то есть описание их сигнатур) без их реализации.

Хотя данная конструкция для нас в новинку, само понятие интерфейса используется на протяжении всего курса. В первую очередь это рассуждения о типах. Для оперирования точками на плоскости нам не нужна «реализация» точек. Достаточно того, что мы представляем их визуально и знаем операции, выполняемые над ними. То же самое касается и более базовых концепций, например, чисел и любых арифметических операций. Задумывались ли вы над тем, как на самом деле выполняются арифметические операции? Ответ на этот вопрос гораздо сложнее, чем может показаться на первый взгляд, и он зависит не только от языка, но и от конкретного аппаратного обеспечения (железа). Однако незнание ответа не мешает нам пользоваться числами, строками и массивами, не зная их устройства.

 // file: DecartPointInterface.php namespace App; // Интерфейсы, по аналогии с классами, хранятся в своих собственных файлах // и загружаются автоматически при следовании стандарту PSR-4. // Имя интерфейса может быть любым, главное — соответствие PSR-4. interface DecartPointInterface  public function __construct($x, $y); public function getX(); public function getY(); > 

То, что раньше мы описывали словами и держали в голове, теперь явно записано в виде кода. Декартова точка — это АТД с тремя операциями:

  • Создание точки из двух значений
  • Извлечение координаты X
  • Извлечение координаты Y

По сути, прикладному коду больше ничего знать о точках и не нужно. Естественно, если нам понадобятся новые операции, то мы всегда можем их добавить, тем самым расширив интерфейс. Свои собственные АТД можно менять как угодно и когда угодно, только учтите, что изменение интерфейса обычно приводит к необходимости править код, использующий его.

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

 namespace AnotherApp; // Импорт интерфейса use App\DecartPointInterface; class DecartPoint implements DecartPointInterface  private $x; private $y; // Интерфейсные функции public function __construct($x, $y)  $this->x = $x; $this->y = $y; > public function getX()  return $this->x; > public function getY()  return $this->y; > // Не интерфейсные функции public function __toString()  return "($this->getX()>, $this->getY()>)"; > > 

Реализация интерфейса происходит за счёт ключевого слова implements , за которым идёт название интерфейса. Интерпретатор проверяет, чтобы в классе были описаны все функции интерфейса и их сигнатуры совпадали, а если это не так, то возникает ошибка. Реализация интерфейса никак не ограничивает возможности по наполнению класса, другими словами, вы можете определять и добавлять в класс все, что хотите, помимо интерфейсных функций.

Насколько наличие интерфейсов критично для PHP программ? Например, можно в любой программе открыть все файлы с классами и удалить часть определения класса, которая описывает реализацию интерфейсов (слово implements и то, что идёт за ним). После этого не изменится ровным счётом ничего — программа продолжит выполняться так же, как и выполнялась. Но ситуация меняется, если использовать интерфейс в сигнатурах функций и методов вместо классов.

 function compare(DecartPointInterface $point1, DecartPointInterface $point2)  // . > 

Во время выполнения программы PHP проверяет, реализует ли класс соответствующий интерфейс, и если нет, то возникает ошибка. Причём проверка идёт именно на наличие записи implements в определении класса, а не на факт того, что методы определены (проверка реализации интерфейса гарантирует это).

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

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

 // Пример из библиотеки DS, внутри которой реализованы различные структуры данных в объектном синтаксисе // https://github.com/php-ds/polyfill/blob/master/src/Stack.php class Stack implements \IteratorAggregate, \ArrayAccess, Collection  // some code > 

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

Если у разных интерфейсов окажутся методы с одинаковым названием, но разной сигнатурой, то возникнет ошибка. В таком случае придётся отказаться от одного из интерфейсов либо переделать их (например, переименовать методы)

Интерфейс Countable

В PHP встроен интерфейс Countable , а функция count умеет работать с любым объектом, реализующим этот интерфейс.

 class Collection implements Countable  private $items; public function __construct($items = [])  $this->items = $items; > public function count()  return sizeof($this->items); > > $coll = new Collection([3, 2, 5]); print_r(count($coll)); // => 3 

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

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: Всё, что вы хотели узнать об интерфейсах, совместимости сигнатур и не побоялись узнать

image

Интерфейсы, впервые появившись в PHP 5, давно уже заняли прочное место в объектно-ориентированной (или всё-таки правильнее «класс-ориентированной»?) части языка.

Казалось бы — что может быть проще интерфейса? «Как бы класс, но и не класс, нельзя создать экземпляр, скорее контракт для будущих классов, содержит в себе заголовки публичных методов» — не правда ли, именно такими словами вы чаще всего отвечаете на собеседовании на дежурный вопрос о том, что такое интерфейс?

Однако не всё так просто, как может показаться начинающему программисту на PHP. Привычные аналогии не работают, руководство по языку вводит вас в заблуждение, в коде таятся неожиданные «подводные камни»…

Три предыдущие части:

  • Готовимся к собеседованию по PHP: ключевое слово «static»
  • Готовимся к собеседованию по PHP: псевдотип «callable»
  • Готовимся к собеседованию по PHP: Всё об итерации и немного про псевдотип «iterable»

Что может содержать интерфейс?

Очевидно, что публичные методы, причем без реализации: сразу после заголовка (сигнатуры) метода следует закончить его точкой с запятой:

interface SomeInterface

Чуть менее очевиден (хотя и описан в мануале) тот факт, что интерфейс может содержать константы (разумеется, только публичные!):

interface SomeInterface < public const STATUSES = [ 'OK' =>0, 'ERROR' => 1, ]; > if (SomeInterface::STATUSES['OK'] === $status) < // . >

Почему же константы в интерфейсах не получили широкого распространения в промышленном коде, хотя и используются иногда? Причина в том, что их невозможно переопределить в интерфейсе-наследнике или в классе, реализующем данный интерфейс. Константы интерфейсов — самые константные константы в мире 🙂

Чего не может содержать интерфейс?

Больше ничего не может. Кроме заголовков публичных методов и публичных констант.

Нельзя включать в интерфейс:

  • Любые свойства
  • Непубличные методы
  • Методы с реализацией
  • Непубличные константы

Совместимость сигнатур методов

Для дальнейшего изучения интерфейсов нам с вами нужно узнать о важнейшем понятии, которое незаслуженно обойдено вниманием в мануале по PHP: о понятии «совместимости сигнатур».

Сигнатура — это описание функции (метода), включающее в себя:

  • Модификатор доступа
  • Имя функции (метода)
  • Список аргументов, где для каждого аргумента указано:

  • Тип
  • Имя
  • Значение по умолчанию
  • либо оператор «три точки»
function (); public function foo($arg = null); protected function sum(int $x, int $y, . $args): int; 

Предположим, что у нас есть две функции, A и B.
Сигнатура функции B считается совместимой с A (порядок важен, отношение несимметрично!) в строгом смысле, если:

Они полностью совпадают

Тривиальный случай, комментировать тут нечего.

B добавляет к A аргументы по умолчанию
function foo($x); 
function foo($x, $y = null); function foo($x, . $args); 
B сужает область значений A
function foo(int $x); 
// В A допускался возврат любых значений, в B эта область сужена только до целых чисел function foo(int $x): int; 

Теперь, когда мы ввели эти три простых правила совместимости определений, станет гораздо проще понять дальнейшие тонкости, связанные с интерфейсами.

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

Интерфейсы могут наследоваться друг от друга:

interface First < public const PI = 3.14159; public function foo(int $x); >interface Second extends First < public const E = 2.71828; public function bar(string $s); >assert(3.14159 === First::PI); assert(true === method_exists(First::class, 'foo')); assert(3.14159 === Second::PI); assert(2.71828 === Second::E); assert(true === method_exists(Second::class, 'foo')); assert(true === method_exists(Second::class, 'bar')); 

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

В интерфейсе-наследнике можно переопределить метод из родительского интерфейса. Но только при условии, что либо его сигнатура будет в точности совпадать с сигнатурой родительского, либо будет совместима (см. предыдущий раздел):

interface First < public function foo(int $x); >interface Second extends First < // Так можно, но бессмысленно public function foo(int $x); // Так нельзя, фатальная ошибка Declaration must be compatible public function foo(int $x, int $y); // Так можно, потому что эта сигнатура совместима с родительской - мы просто добавили необязательный аргумент public function foo(int $x, int $y = 0); // Так тоже можно, все аргументы после ". " являются необязательными public function foo(int $x, . $args); // И так тоже можно public function foo(int $x, . $args): int; >

Если ли в PHP множественное наследование?

Если вам зададут такой вопрос, смело отвечайте: «да». Интерфейс может наследоваться от нескольких других интерфейсов.

Теперь вы видели всё:

interface First < public function foo(int $x); >interface Second < public function bar(string $s); >interface Third extends First, Second < public function baz(array $a); >assert(true === method_exists(Third::class, 'foo')); assert(true === method_exists(Third::class, 'bar')); assert(true === method_exists(Third::class, 'baz')); 

Правила решения конфликтов сигнатур методов при множественном наследовании точно такие же, как мы уже видели выше:

— либо сигнатуры совпадают полностью
— либо сигнатура метода интерфейса, упомянутого в списке предков первым, должна быть совместима с сигнатурой из второго предка (да, порядок упоминания имеет значение, но это очень редкий кейс, просто не принимайте его никогда во внимание)

Тонкости реализации интерфейсов

Собственно, после всего, что вы уже видели, это уже и не тонкости, а так, мелкие нюансы.

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

interface IntSumInterface < public function sum(int $x, int $y): int; >interface IntMultInterface < public function mult(int $x, int $y): int; >class Math implements IntSumInterface, IntMultInterface < public function sum(int $x, int $y): int < return $x + $y; >public function mult(int $x, int $y): int < return $x * $y; >> 

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

Как быть, если в разных интерфейсах, которые реализует класс, будет один и тот же метод (с одинаковым названием)? Смотри выше — также, как и при наследовании интерфейсов друг от друга должен соблюдаться принцип совместимости сигнатур.

И да. Не верьте мануалу, который провозглашает:

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

The class implementing the interface must use the exact same method signatures as are defined in the interface. Not doing so will result in a fatal error.

Всё не так, действует тоже самое правило совместимости:

interface SomeInterface < public function sum(int $x, int $y); >class SomeClass implements SomeInterface < public function sum(int $x, int $y): int или public function sum(int $x, int $y, int $z = 0): int или даже public function sum(int $x, int $y, . $args): int < // реализация метода >> 

Интерфейс — это класс? Pro et Contra

Вообще-то нет. Интерфейс — это интерфейс, он отличается от класса хотя бы тем, что нельзя создать «экземпляр интерфейса».

И вообще-то да, у них в PHP очень много общего:

  1. Интерфейсы, как и классы, могут находиться в пространстве имён.
  2. Интерфейсы, как и классы, можно загружать через механизм автозагрузки. Функции автозагрузки будет передано полное имя интерфейса (с пространством имён).
  3. В каждом интерфейсе есть предопределенная константа ThisInterface::class, содержащая его полное имя
  4. Интерфейс, как и класс, может участвовать справа в операторе instanceof
  5. Интерфейс, как и класс, может быть указан в качестве типа в тайп-хинтинге (указание типа аргумента либо возвращаемого значения функции)

Что почитать в ночь перед ответственным собеседованием?

Разумеется, мануал по языку:

  • php.net/manual/ru/language.oop5.interfaces.php
  • php.net/manual/ru/language.oop5.constants.php
  • php.net/manual/ru/language.constants.predefined.php

Системный подход к самообразованию в программировании очень важен. И, по моему мнению, неплохо в начале пути в IT помогают структурировать самообучение вебинары и краткосрочные курсы. Именно поэтому я рекомендую (и немного скромно рекламирую) даже опытным разработчикам посещать разовые вебинары и курсы повышения квалификации — результат при грамотном сочетании курсов и самоподготовки всегда налицо!

Успехов на собеседовании и в работе!

Интерфейсы в ООП на 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 выдаст нам фатальную ошибку. Давайте реализуем также класс Rectangle :

Сделайте класс Disk ( круг ), реализующий интерфейс Figure .

Замечание

Как уже было написано выше, не может быть интерфейса и класса с одинаковым названием. Это создает некоторые проблемы с придумыванием названий. Например, мы хотим сделать класс User , реализующий интерфейс User . Как мы видим, у нас конфликт имен. Для его разрешения, нужно или класс назвать по-другому, или интерфейс.

Общепринято в таком случае название интерфейса начать с маленькой буквы i , чтобы показать, что это интерфейс, а не класс. То есть в нашем случае мы сделаем интерфейс iUser , а реализовывать его будет класс User . Такой подход мы иногда будем применять в следующих уроках.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *