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

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

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

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


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

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

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

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

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

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

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

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

Самое приятное — кто теперь ловит ошибку: передача пингвина в airShow больше не аварийное завершение во время выполнения, а ошибка компиляции. Программа просто не соберётся.

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

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

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

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

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

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