Небольшие интерфейсы
Принцип разделения интерфейсов гласит: ни один класс не должен быть вынужден реализовывать методы, которые ему не нужны. Вместо одного «толстого» интерфейса со множеством методов сделайте несколько небольших — и каждый класс реализует только то, что верно для него.
Интерфейсы PHP естественно подходят под это правило: класс объявляет ровно те интерфейсы, которые реализует, — и ничего больше, — а когда какому-то коду действительно нужно несколько способностей, небольшие интерфейсы объединяются через extends. Пока каждый интерфейс остаётся крошечным, любой список implements остаётся честным.
«Толстый» интерфейс
Интерфейс Worker с методами work() и eat() выглядит безобидно — пока на работу не устроится робот. Робот прекрасно работает, но ему придётся реализовать и eat(), ответив... хоть чем-нибудь. Такие бессмысленные ответы — верный признак «толстого» интерфейса.
Робот, вынужденный есть
Робот реализует eat() только потому, что этого требует интерфейс. Обеденный перерыв выводит ответ, которого вообще не должно существовать, — класс был вынужден соврать.
Вывод: PHP-скрипт
<?php
// Один «толстый» интерфейс — каждый работник ОБЯЗАН ещё и есть.
interface Worker {
public function work(): string;
public function eat(): string;
}
class Human implements Worker {
public function work(): string {
return "человек: работает";
}
public function eat(): string {
return "человек: обедает";
}
}
class Robot implements Worker {
public function work(): string {
return "робот: работает";
}
// Навязано интерфейсом — роботы не едят.
public function eat(): string {
return "робот: ОШИБКА, не может есть";
}
}
$team = [new Human(), new Robot()];
foreach ($team as $w) {
echo $w->work(), "\n";
}
foreach ($team as $w) {
echo $w->eat(), "\n";
}
Разделите интерфейс
Разделите «толстый» интерфейс на Worker и Eater. Человек реализует оба, робот — только Worker. Рабочая смена принимает работников, обеденный перерыв — тех, кто ест, и робота просто никогда не приглашают на обед, а ни один метод не врёт.
Каждый класс реализует только свою правду
Два небольших интерфейса вместо одного «толстого». У робота больше вообще нет метода eat() — указание типа Eater автоматически не пускает его на обеденный перерыв.
Вывод: PHP-скрипт
<?php
// Два небольших интерфейса — каждый класс реализует только то, что верно для него.
interface Worker {
public function work(): string;
}
interface Eater {
public function eat(): string;
}
class Human implements Worker, Eater {
public function work(): string {
return "человек: работает";
}
public function eat(): string {
return "человек: обедает";
}
}
class Robot implements Worker {
public function work(): string {
return "робот: работает";
}
}
function workShift(Worker ...$team): void {
foreach ($team as $w) {
echo $w->work(), "\n";
}
}
function lunchBreak(Eater ...$guests): void {
foreach ($guests as $e) {
echo $e->eat(), "\n";
}
}
workShift(new Human(), new Robot());
lunchBreak(new Human());
Небольшие части объединяются
Небольшие интерфейсы — это не ограничение: они объединяются. Когда какой-то функции действительно нужны обе способности, соедините их в один интерфейс: WorkingEater extends Worker, Eater.
Вот и весь приём: определяйте небольшие интерфейсы и склеивайте их через extends только там, где большего действительно требуется.
Объединяйте, когда нужны обе способности
WorkingEater собран из двух небольших интерфейсов с помощью extends. Человек объявляет этот один интерфейс и тем самым считается и Worker, и Eater: полному рабочему дню нужны обе способности, поэтому подходит только человек.
Вывод: PHP-скрипт
<?php
interface Worker {
public function work(): string;
}
interface Eater {
public function eat(): string;
}
// Небольшие интерфейсы при необходимости объединяются в более крупные.
interface WorkingEater extends Worker, Eater {
}
class Human implements WorkingEater {
public function work(): string {
return "человек: работает";
}
public function eat(): string {
return "человек: обедает";
}
}
function fullDay(WorkingEater $we): void {
echo $we->work(), "\n";
echo $we->eat(), "\n";
echo $we->work(), "\n";
}
fullDay(new Human());