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

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

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

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


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

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

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

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

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

Prompt
Напишите программу на Python, показывающую нарушение принципа подстановки Лисков: протокол Bird с методом fly, класс Sparrow, который летает, и класс Penguin, чей метод fly выбрасывает исключение, потому что пингвины не умеют летать. Функция air_show вызывает fly у каждой птицы; перехватите исключение, чтобы показать сообщение о сбое, когда выступает пингвин.
Вывод: полная программа на Python

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

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

Самое приятное — кто теперь замечает ошибку: инструмент проверки типов (например, mypy) сообщает об air_show([Penguin()]) ещё до запуска кода, потому что у пингвина нет метода fly(), — обычный python3 упал бы только в момент вызова fly().

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

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

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

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

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

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