- PVSM.RU - https://www.pvsm.ru -
Интерфейсы, впервые появившись в PHP 5, давно уже заняли прочное место в объектно-ориентированной (или всё-таки правильнее «класс-ориентированной»?) части языка.
Казалось бы — что может быть проще интерфейса? "Как бы класс, но и не класс, нельзя создать экземпляр, скорее контракт для будущих классов, содержит в себе заголовки публичных методов" — не правда ли, именно такими словами вы чаще всего отвечаете на собеседовании на дежурный вопрос о том, что такое интерфейс?
Однако не всё так просто, как может показаться начинающему программисту на PHP. Привычные аналогии не работают, руководство по языку вводит вас в заблуждение, в коде таятся неожиданные «подводные камни»…
Три предыдущие части:
Очевидно, что публичные методы, причем без реализации: сразу после заголовка (сигнатуры) метода следует закончить его точкой с запятой:
interface SomeInterface
{
public function foo();
public static function bar(Baz $baz);
}
Чуть менее очевиден (хотя и описан в мануале) тот факт, что интерфейс может содержать константы (разумеется, только публичные!):
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;
Две сигнатуры А и B (порядок важен, отношение совместимости несимметрично!) называются совместимыми (B считается совместимой с A) в строгом смысле, если:
Тривиальный случай, комментировать тут нечего.
A:
function foo($x);
совместимые B:
function foo($x, $y = null);
function foo($x, ...$args);
A:
function foo(int $x);
совместимые B:
// В 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;
}
Если вам зададут такой вопрос, смело отвечайте: «да». Интерфейс может наследоваться от нескольких других интерфейсов.
Теперь вы видели всё:
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
{
// реализация метода
}
}
Вообще-то нет. Интерфейс — это интерфейс, он отличается от класса хотя бы тем, что нельзя создать «экземпляр интерфейса».
И вообще-то да, у них в PHP очень много общего:
Разумеется, мануал по языку:
Но гораздо лучше читать тяжелые технические тексты не в последнюю ночь, а заранее.
Системный подход к самообразованию в программировании очень важен. И, по моему мнению, неплохо в начале пути в IT помогают структурировать самообучение вебинары и краткосрочные курсы. Именно поэтому я рекомендую даже опытным разработчикам посещать разовые вебинары и курсы повышения квалификации — результат при грамотном сочетании курсов и самоподготовки всегда налицо!
Успехов на собеседовании и в работе!
Автор: AlexLeonov
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/interfejsy/255517
Ссылки в тексте:
[1] Готовимся к собеседованию по PHP: ключевое слово «static»: https://habrahabr.ru/post/259627/
[2] Готовимся к собеседованию по PHP: псевдотип «callable»: https://habrahabr.ru/post/259991/
[3] Готовимся к собеседованию по PHP: Всё об итерации и немного про псевдотип «iterable»: https://habrahabr.ru/post/324934/
[4] php.net/manual/ru/language.oop5.interfaces.php: http://php.net/manual/ru/language.oop5.interfaces.php
[5] php.net/manual/ru/language.oop5.constants.php: http://php.net/manual/ru/language.oop5.constants.php
[6] php.net/manual/ru/language.constants.predefined.php: http://php.net/manual/ru/language.constants.predefined.php
[7] Источник: https://habrahabr.ru/post/328890/
Нажмите здесь для печати.