Я следил за этим высоко оцененным вопросом о возможном нарушении принципа подстановки Лискова. Я знаю, что такое принцип подстановки Лискова, но мне все еще неясно, что может пойти не так, если я, как разработчик, не задумываюсь над этим принципом при написании объектно-ориентированного кода.
27
Ответы:
Я думаю, что в этом вопросе очень хорошо сказано, что является одной из причин, по которой так высоко проголосовали.
Представьте, если вы будете:
В этом методе иногда вызывается вызов .Close (), поэтому теперь, основываясь на конкретной реализации производного типа, вы должны изменить способ поведения этого метода по сравнению с тем, как этот метод был бы написан, если бы в Task не было подтипов, которые могли бы быть передал этот метод.
Из-за нарушений подстановки лиски код, использующий ваш тип, должен иметь четкие знания о внутренней работе производных типов, чтобы трактовать их по-разному. Это тесно связывает код и, как правило, затрудняет последовательное использование реализации.
источник
Если вы не выполняете контракт, который был определен в базовом классе, все может молча провалиться, когда вы получите результаты, которые отключены.
LSP в википедии говорится
Если что-то из этого не выполняется, вызывающий абонент может получить результат, которого он не ожидает.
источник
Рассмотрим классический случай из анналов вопросов на собеседование: вы получили Circle из Ellipse. Зачем? Потому что круг - это эллипс, конечно!
Кроме ... Эллипс имеет две функции:
Очевидно, что они должны быть переопределены для круга, потому что круг имеет равномерный радиус. У вас есть две возможности:
Большинство ОО-языков не поддерживают второй, и на то есть веская причина: было бы удивительно обнаружить, что ваш Круг больше не является Кругом. Так что первый вариант самый лучший. Но рассмотрим следующую функцию:
Представьте, что some_function вызывает e.set_alpha_radius. Но поскольку e действительно был Кругом, у него, к удивлению, также установлен его бета-радиус.
И в этом заключается принцип замещения: подкласс должен быть заменяемым для суперкласса. В противном случае происходит неожиданное.
источник
По словам непрофессионала:
В вашем коде будет очень много предложений CASE / switch .
Каждое из этих предложений CASE / switch будет время от времени нуждаться в новых случаях, что означает, что кодовая база не так масштабируема и обслуживаема, как должна быть.
LSP позволяет коду работать как аппаратное обеспечение:
Вам не нужно менять iPod, потому что вы купили новую пару внешних динамиков, поскольку и старые, и новые внешние динамики соответствуют одному и тому же интерфейсу, они взаимозаменяемы без потери iPod нужной функциональности.
источник
typeof(someObject)
чтобы решить, что вам «разрешено делать», то конечно, но это совсем другой паттерн.привести пример из реальной жизни с помощью Java UndoManager
он наследует от того,
AbstractUndoableEdit
чей контракт указывает, что он имеет 2 состояния (отменено и переделано) и может переходить между ними с помощью одного вызоваundo()
иredo()
однако UndoManager имеет больше состояний и действует как буфер отмены (каждый вызов
undo
отменяет некоторые, но не все изменения, ослабляя постусловие)это приводит к гипотетической ситуации, когда вы добавляете UndoManager в CompoundEdit перед вызовом, а
end()
затем вызываете отмену для этого CompoundEdit, что приведет к его вызовуundo()
при каждом редактировании, после того как ваши изменения будут частично отмененыЯ свернул свой собственный,
UndoManager
чтобы избежать этого (я, вероятно, должен переименовать его,UndoBuffer
хотя)источник
Пример: вы работаете с UI-фреймворком и создаете свой собственный пользовательский UI-элемент управления путем создания подкласса
Control
базового класса.Control
Базовый класс определяет метод ,getSubControls()
который должен возвращать коллекцию вложенных элементов управления (если таковые имеются). Но вы переопределяете метод, чтобы фактически вернуть список дат рождения президентов США.Так что может пойти не так с этим? Очевидно, что рендеринг элемента управления не удастся, так как вы не возвращаете список элементов управления, как ожидалось. Скорее всего, пользовательский интерфейс будет сбой. Вы нарушаете договор, которого должны придерживаться подклассы Control.
источник
Вы также можете посмотреть на это с точки зрения моделирования. Когда вы говорите, что экземпляр класса
A
также является экземпляром класса,B
вы подразумеваете, что «наблюдаемое поведение экземпляра классаA
также может быть классифицировано как наблюдаемое поведение экземпляра классаB
» (это возможно только в том случае, если классB
менее специфичен, чем классA
.)Таким образом, нарушение LSP означает, что в вашем проекте есть некоторое противоречие: вы определяете некоторые категории для своих объектов, а затем не соблюдаете их в своей реализации, что-то должно быть не так.
Как сделать коробку с тегом: «Эта коробка содержит только синие шары», а затем бросить в нее красный шар. Какая польза от такого тега, если он показывает неверную информацию?
источник
Недавно я унаследовал кодовую базу, в которой есть несколько крупных нарушителей Лискова. В важных классах. Это вызвало у меня огромное количество боли. Позвольте мне объяснить, почему.
У меня есть
Class A
, что вытекает изClass B
.Class A
иClass B
поделиться кучей свойств, которыеClass A
переопределяют с его собственной реализацией. Установка или получениеClass A
свойства по-разному влияет на установку или получение точно такого же свойстваClass B
.Если оставить в стороне тот факт, что это очень ужасный способ перевода в .NET, у этого кода есть ряд других проблем.
В этом случае
Name
используется в качестве индекса и переменной управления потоком в ряде мест. Вышеуказанные классы замусорены по всей кодовой базе как в необработанном, так и в производном виде. Нарушение принципа подстановки Лискова в этом случае означает, что мне нужно знать контекст каждого отдельного вызова каждой из функций, которые принимают базовый класс.Код использует объекты обоих
Class A
иClass B
, поэтому я не могу просто сделатьClass A
абстракцию, чтобы заставить людей использоватьClass B
.Есть несколько очень полезных служебных функций, которые работают,
Class A
и другие очень полезные служебные функции, которые работаютClass B
. В идеале я хотел бы иметь возможность использовать любую функцию полезности , которая может работать наClass A
вClass B
. Многие из функций, которые принимают,Class B
можно легко выполнить,Class A
если бы не нарушение LSP.Хуже всего то, что этот конкретный случай действительно трудно реорганизовать, так как все приложение зависит от этих двух классов, работает с обоими классами все время и сломается сотнями способов, если я изменю это (что я собираюсь сделать так или иначе).
Чтобы это исправить, мне нужно создать
NameTranslated
свойство, которое будет являтьсяClass B
версиейName
свойства и очень, очень тщательно изменять каждую ссылку на производноеName
свойство, чтобы использовать мое новоеNameTranslated
свойство. Однако, если хотя бы одна из этих ссылок неверна, все приложение может взорваться.Учитывая, что кодовая база не имеет модульных тестов, это очень близко к тому, чтобы быть самым опасным сценарием, с которым может столкнуться разработчик. Если я не изменю нарушение, мне придется потратить огромное количество умственной энергии, отслеживая, над каким типом объекта оперируют в каждом методе, и если я исправлю нарушение, я могу в любой момент взорвать весь продукт.
источник
BaseName
иTranslatedName
получите доступ как к стилю класса A, такName
и к значению класса B? Тогда любая попытка доступаName
к переменной типаB
будет отклонена с ошибкой компилятора, так что вы можете убедиться, что все ссылки были преобразованы в одну из других форм.Если вы хотите почувствовать проблему нарушения LSP, подумайте, что произойдет, если у вас есть только .dll / .jar базового класса (без исходного кода) и вам нужно создать новый производный класс. Вы никогда не сможете выполнить эту задачу.
источник