Перейти к основному содержимому

Держите обещание

Принцип подстановки Лисков гласит: везде, где код ожидает интерфейс, каждый класс, реализующий его, должен работать корректно. Реализация, которая формально подходит, но ведёт себя плохо — бросает исключение, ничего не делает, обманывает, — ломает каждую функцию, которая доверилась интерфейсу.

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


Замена, которая нарушает договор

Интерфейс Bird обещает fly(). Воробьи обещание выполняют. Пингвины тоже птицы — поэтому кажется естественным реализовать интерфейс... через throw, ведь пингвины не умеют летать.

Теперь airShow, которая работает с любой честной птицей, падает на пингвине. Ошибка не в airShow — она в замене, которая нарушила обещание.

Пингвин, который срывает шоу

airShow доверяет интерфейсу и вызывает fly() у каждой птицы. Воробей выступает; пингвин бросает исключение и обрушивает всё шоу (try/catch лишь смягчает падение, чтобы показать сообщение).

Prompt
Напишите PHP-скрипт, показывающий нарушение принципа подстановки Лисков: интерфейс Bird с методом fly, класс Sparrow, который летает, и класс Penguin, у которого fly бросает RuntimeException, потому что пингвины не умеют летать. Функция airShow вызывает fly у каждой птицы; используйте try/catch, чтобы показать сообщение о сбое, когда выступает пингвин.
Вывод: PHP-скрипт

Разделите обещания

Решение: обещать только то, что верно. Разделите раздутое обещание на Flyer и Swimmer. Воробей реализует Flyer, пингвин реализует Swimmer — и ни один класс не подписывает контракт, который не может выполнить.

Самое приятное — кто теперь ловит ошибку: airShow объявляет тип Flyer, поэтому airShow(new Penguin()) не проходит проверку типов — PHP останавливает вызов с TypeError прежде, чем выступит хоть одна птица, а статический анализатор сообщит об этой строке ещё до запуска кода.

Каждый класс держит своё обещание

airShow принимает Flyer, poolShow принимает Swimmer. Каждое животное появляется только там, где его обещание выполняется, — подстановка безопасна по построению.

Prompt
Исправьте нарушение принципа Лисков в PHP, разделив обещания: интерфейс Flyer с методом fly и интерфейс Swimmer с методом swim. Sparrow реализует Flyer, Penguin реализует Swimmer. Напишите airShow для летающих и poolShow для плавающих и запустите обе функции, чтобы каждый класс появлялся только там, где его обещание выполняется.
Вывод: PHP-скрипт
Every class keeps its promise

Любой Flyer может присоединиться

Выгода честных интерфейсов: новый класс Eagle присоединяется к воздушному шоу без риска — всё, что реализует Flyer, действительно летает, поэтому airShow никогда не нужно сомневаться в своих входных данных.

Prompt
Расширьте PHP-пример с интерфейсом Flyer новым классом Eagle и проведите воздушное шоу с несколькими летающими, показав, что любая честная реализация Flyer безопасно подставляется.
Вывод: PHP-скрипт