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

Недавно я видел рекламу для Nintendo Switch, которая была довольно милой. Этот парень играл в Zelda на своем портативном устройстве, лежа в своей постели, затем встал и пошел в свою гостиную, подключил устройство к док-станции, взял контроллер и вдруг начал играть на своем телевизоре. Точно такая же игра, теперь на телевидении!

Как вы думаете, создатели этой игры создали одну версию игры для портативных устройств и другую версию для телевизора? Возможно нет. На самом деле, разработчики, вероятно, хотели сделать два опыта максимально совместимыми.

Если игрок нажимает кнопку A, персонаж в игре должен прыгать. Не имеет значения, подключена ли эта кнопка A к контроллеру, и игрок смотрит в игру на большом экране, или кнопка A находится на самом портативном устройстве. A означает прыжок. В этом случае меняется только то, как игра представлена ​​пользователю.

И портативное устройство, и контроллер должны соответствовать одному и тому же интерфейсу. На КПК не может быть секретных кнопок, которых нет на контроллере. Если кнопка A означает «прыжок» на портативном устройстве, в реализации контроллера A не может означать «присесть». Пользователь ожидает единообразия на обеих платформах, иначе этот трюк не был бы таким привлекательным.

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

Если бы мы кодировали царство животных, marsupial был бы интерфейсом, требующим, чтобы животное носило своих детенышей в сумке. Если животное не носит детенышей в сумке, то оно не сумчатое и, следовательно, не может соответствовать нашему marsupial интерфейсу. Если бы я сказал вам изобразить сумчатое животное, вы, вероятно, не могли бы не представить животных, несущих эту черту, таких как коала или кенгуру. Это потому, что интерфейс должен представлять более абстрактную концепцию.

Если бы мы использовали классы, мы, вероятно, использовали бы что-то вроде ArcticWolf в качестве базового класса, который расширил бы его поведение до AlaskanHusky среди других пород собак. Хаски унаследовали некоторые черты арктических волков, такие как безжалостная выносливость и густой мех. Однако, как мы оба знаем, хаски не арктический волк — он просто наследует от него многие общие черты поведения. Однако арктический волк может существовать сам по себе, потому что у него есть собственное поведение по умолчанию.

С другой стороны, кенгуру является сумчатым. Он не наследует какое-либо поведение от сумчатых, потому что «сумчатый» — это термин, используемый для классификации группы животных, обладающих определенной характеристикой. Сумчатое животное само по себе не является животным.

Имейте в виду, что между теми, кто наследует один и тот же базовый класс, будет гораздо больше общего, чем между теми, которые соответствуют одному и тому же интерфейсу. Разработчик чаще всего обращается к структуре наследования на основе классов, когда имеет дело с несколькими компонентами, которые имеют одинаковое поведение между функциями, чтобы СУШИТЬ свой код и упростить распространение этого поведения на новые модули в будущем. . Интерфейс чаще используется, когда разработчик хочет расширить функциональность существующего модуля, создав более широкий спектр реализаций для частей, с которыми он в настоящее время взаимодействует. Обычно я не являюсь поклонником коротких, броских высказываний, используемых для обобщения сложных тем, но я думаю, что это неплохо справляется со своей задачей.

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