[Программирование] Деконструкция LSP

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
30-Сен-2020 01:31

Здравствуйте, меня зовут Дмитрий Карловский. А вы на канале Core Dump, где мы берём разные темы из компьютерной науки и деконструируем их по полочкам. А на этот раз мы начнём деконструировать принципы SOLID начиная с наиболее конкретного.
В далёком 1987 году Барбара Лисков сформулировала принцип разработки имени себя.
Он позволяет понять правильно вы написали полиморфный код или нет. Но прежде чем его сформулировать нам надо разобраться с некоторыми понятиями, которые входят в определение..
Видео запись этого разбора.
Отношение "супертип-подтип"
Все данные в нашей программе принадлежат тому или иному типу. Тип определяет множество возможных значений и их семантику. Один тип может полностью включать в себя другой. В таком случае второй тип является подтипом первого. Таким образом типы могу образовывать иерархию. Рассмотрим пример с числами..

Как тип целых так и тип положительных чисел по отдельности являются частными случаями типа вещественных чисел, а значит являются его подтипами. В то же время целые не включают в себя все положительные. А положительные не включают в себя все целые. Поэтому эти типы не состоят друг с другом в отношении "супертип-подтип". А вот натуральные числа являются одновременно и целыми и положительными, поэтому тип натуральных чисел является подтипом обоих этих типов.
Отношение "супертип-подтип" является транзитивным, то есть если один тип является подтипом другого, а другой — третьего, то и первый является подтипом третьего.
Сильная и слабая типизация
Тут правда необходимо иметь ввиду специфику представления значения разных типов в памяти. Например, эквивалентные значения целочисленного и вещественного типов обычно представляются разной последовательностью бит. Так что в некоторых языках эти типы не будут находиться в родственных отношениях. Однако, есть языки, умеющие автоматически преобразовывать значения из одного представления в другое — в них более узкий тип является подтипом более широкого, который позволяет хранить большее число вариантов значений.

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

В данном примере, процедура draw у нас принимает на вход произвольную фигуру. И какую бы фигуру мы ей ни передали — процедура её всё равно нарисует.
Суть LSP — повсеместная ковариантность
Наконец, теперь мы можем сформулировать принцип подстановки Барбары Лисков: "любые функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа, не зная об этом и не нарушая желаемых свойств программы".

Например, если функция принимает на вход питомца, то передать ей можно хоть кошечку, хоть собачку, а вот дикую лису нельзя, иначе откусит палец. Говоря современным языком, принцип LSP гласит: все параметры всех функций должны быть ковариантными, то есть ограничивающими дерево типов сверху относительно задекларированного для данного параметра.
Звучит вроде бы логично, однако...
Контравариантность не вписывается в LSP
Давайте рассмотрим функцию, которая принимает на вход коробку для питомца и засовывает в неё какого-то, нам не известного, питомца.

Если мы позволим передавать в неё клетку для собак, то вскоре можем столкнуться с абсурдной ситуацией, когда вытащим оттуда казалось бы собаку и скажем "Апорт", а она замяукает и нассыт вам в тапки. То есть подтип передавать в эту функцию нельзя. А вот супертип очень даже можно, ведь в клетку для животных можно засовывать хоть домашнего, хоть дикого животного. Получается своего рода инверсия LSP. То есть иерархия типов ограничивается не сверху, а снизу. Такое ограничение имеет название "контравариантность".
Виды вариантностей
Получается, что возможность подстановки одного типа вместо другого зависит не столько от типа параметра, сколько от того, что функция с этим параметром делает. И тут могут быть самые разные ограничения..
  • Только чтение — ковариантность (ограничение сверху)
  • Только запись — контравариантность (ограничение снизу)
  • Чтение и запись:
    • инвариантность (ограничение снизу и сверху)
    • бивариантность (без ограничений)

То есть, если мы хотим писать корректные программы, то мы вынуждены явно нарушать LSP во многих случаях.
Применимость LSP
Все эти разные вариантности появляются лишь когда мы изменяем какое-то состояние. Но если мы работаем лишь с чистыми функциями, которые ничего не изменяют, то ковариантность всех параметров и, как следствие, LSP мы получаем автоматически. Хотим мы того или нет — от нас это не зависит.
  • Функциональное Программирование :-)

С другой стороны, любая небесполезная функциональная программа содержит не только чистую, но и грязную часть, где вопросы вариантности встают в полный рост.
Правильный LSP
Ладно, давайте пофантазируем и попробуем сформулировать LSP здорового человека, учитывающего все озвученные ранее нюансы...
"Функция может не знать конкретный передаваемый ей тип, но она обязана ограничить множество принимаемых типов так, чтобы ей нельзя было передать такой тип, который сломает ожидаемое поведение программы".
Звучит с одной стороны всё ещё так же размыто, как и оригинальная формулировка, а с другой — самоочевидно. Понятное дело, что если есть возможность правильно написать типы, то стоит это делать правильно.
  • Статическая типизация :-)

Следовать ли LSP?
Как было показано ранее, если мы хотим писать корректные программы, то не можем следовать LSP. Ведь это принцип, а не, например, паттерн. Принципа либо придерживаются во всём, либо придерживаются чего-то другого, что лишь иногда соответствует тому принципу. Принципу нельзя придерживаться частично, так же как нельзя быть немножко беременной.
LSP, на текущий момент, — это устаревшая концепция, не учитывающая многие случаи. Поэтому применять её как руководство к действию ни в коем случае нельзя. А важно понимать вариантность ваших параметров и то, как конкретный язык программирования работает с типами.
  • LSP :-(
  • Вариантность :-)

Что ещё почитать о вариантности?
Для лучшего понимания вопроса вариантности рекомендую свою статью на эту тему. Там я доступным языком рассказываю всю эту сложную теорию, и привожу примеры кода на разных языках программирования, иллюстрирующие разные типы вариантности.
К сожалению, в современных языках поддержка вариантности находится в зачаточном состоянии. Где-то она не поддерживается вообще. Где-то вариантности жёстко захардкожены. Где-то есть куцые механизмы явного указания вариантности. И в совсем редких языках можно встретить не только автоматическое выведение типов параметров, но и выведение их вариантности, что очень круто, так как ручное разруливание вариантности порой слишком многословно.
Продолжение следует..
Лайк
Подписка
Комментарий
Поделись-ка
Если данный разбор показался вам полезным, то дайте мне об этом знать посредством лайка. А так же поделитесь ссылкой на него со своими коллегами.
Если же вы не согласны с какой-либо мыслью или, наоборот, чувствуете какую-то недосказанность и хотите дополнить своими идеями, то жду ваших комментариев.
Если вы не боитесь подискутировать со мной в прямом эфире или даже готовы стать соавтором будущих разборов, то пишите телеграмы.
Наконец, подписывайтесь на канал, чтобы не пропустить дальнейшие разборы. Нам ещё много чего с вами нужно будет обсудить.
На этом пока что всё. С вами был немножко программер Дмитрий Карловский.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_programmirovanie (Программирование), #_solid, #_lsp, #_core_dump, #_programmirovanie (
Программирование
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 23-Ноя 01:40
Часовой пояс: UTC + 5