LINUX.ORG.RU

Ответ на: комментарий от Manhunt

«Принцип подстановки Лискова (LSP) - это конкретное определение отношения подтипов, называемое (сильным) поведенческим подтипом.»

Вот прям все и понятно.

Liz812
() автор топика
Ответ на: комментарий от Liz812

Привет, Лизавета.

определение отношения подтипов

В двух словах это означает, что подтип (подкласс в крестах) обязан иметь такой же интерфейс, как и базовый тип (класс), а не больше, или меньше.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Интерфейс - это просто наследуемый набор функций?

А если он наследуется от базового класса это не получается автоматически? Привожу пример есть класс «млекопитающие» - все наследники получают умение дышать, есть, издавать звуки, размножаться и т.д.

Где тут этот принцип работает?

Liz812
() автор топика
Ответ на: комментарий от ya-betmen

ага, женщину мужской фамилией не назовут (нет, не в этом случае)

dave ★★★★★
()
Ответ на: комментарий от Liz812

Привожу пример есть класс «млекопитающие» - все наследники получают умение дышать, есть, издавать звуки, размножаться и т.д.

Ты в наследнике можешь в принципе добавить публичный метод, которого в базовом классе нет. Вот это по Лисков - плохо. Потому что у тебя подкласс делает что-то, что базовый класс не делает. И т.о. нельзя подставить подкласс вместо базового (или наоборот) в произвольное выражение.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 4)
Ответ на: комментарий от no-such-file

Потому что у тебя подкласс делает что-то, что базовый класс не делает.

А почему это плохо и нарушается этот принцип? Разве наследование не придумали специально для этого, расширять базовые классы и уменьшать дублирование кода в программе? Зачем в подклассе «корова» заново реализовывать функцию «дышать» из млекопитающего?

Liz812
() автор топика
Ответ на: комментарий от zhekas

Там очень сложно и слишком подробно, я С++ только начала. Не думаю, что нужно слушать больше часа, чтобы понять принцип. Вот есть фраза LSP «гарантирует семантическую интероперабельность (interoperability) типов в иерархии». А нафиг мне такие гарантии, если корову собакой в иерархии типов «млекопитающие» заменить нельзя?

Liz812
() автор топика
Ответ на: комментарий от Liz812

Разве наследование не придумали специально для этого, расширять базовые классы

Не совсем. Наследование придумали, чтобы подтипы могли делать что-то специфическое. Т.е. на «издать звук» корова делает «му», а собака «гав».

Зачем в подклассе «корова» заново реализовывать функцию «дышать» из млекопитающего?

А где я сказал, что её нужно делать заново? Я сказал, что корова не должна иметь никаких дополнительных возможностей, которых нет у базового «млекопитающее».

почему это плохо

Потому что тогда объекты иерархии не будут взаимозаменяемые. Допустим все «животные» имеют метод «идти». Но некоторые подклассы (не все) имеют ещё метод «рыть». Тогда в условном алгоритме «идти вперёд, если препятствие, то рыть» получится облом, т.к. рыть могут не все и нужно перед «рыть» сначала проверять, а может ли данный конкретный объект рыть. И вот эта проверка в принципе ломает ООП, т.к. оно изначально задумано, чтобы таких явных проверок избежать и дать объектам самим решать что делать.

no-such-file ★★★★★
()
Ответ на: комментарий от Liz812

нафиг мне такие гарантии, если корову собакой в иерархии типов «млекопитающие» заменить нельзя

Должно быть можно. Принцип Лисков как раз это и требует.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

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

Почему это? Вызывающий код же не знает про твой новый метод. Что он есть, что его нет. Вот если ты переопределишь существующий метод таким образом, что он перестанет делать то, что от него ожидает вызывающий код...

Nervous ★★★★★
()
Ответ на: комментарий от no-such-file

Но некоторые подклассы (не все) имеют ещё метод «рыть». Тогда в условном алгоритме «идти вперёд, если препятствие, то рыть»

Так этот алгоритм не применим для класса «животное». В нём ведь метод «рыть» вообще не определён.

Поэтому расширение в общем случае LSP разрешает. Запрещает изменение поведения в подклассах. Например, если для животного «перемещаться вперед» = «перемещаться вдоль земли в направлении взгляда», а птицу мы унаследуем от животного, но для неё будет «перемещаться вперед» = «лететь в направлении взгляда», то LSP нарушится. Потому что алгоритмы, работавшие для любого животного, перестанут работать, если животное — птица.

monk ★★★★★
()
Ответ на: комментарий от Nervous

Вызывающий код же не знает про твой новый метод.

Кстати, есть исключение. Если язык — smalltalk или ruby, то метод на выполнение у объекта можно запросить любой. Если не определён конкретный, то будет выполнен метод method_missing. И в этом случае получается, что поведение производного класса, в котором метод определили существенно отличается от поведения базового класса при вызове этого метода.

monk ★★★★★
()
Ответ на: комментарий от monk

В нём ведь метод «рыть» вообще не определён

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

Запрещает изменение поведения в подклассах

Понятие «поведение» слишком философское. Я сторонник того, чтобы рассматривать «поведение» в контексте синтаксиса языка. Т.е. интерфейсов, контрактов и т.п. явно заданного «поведения».

Потому что алгоритмы, работавшие для любого животного, перестанут работать, если животное — птица

Тогда это плохие алгоритмы, которые полагались на неявно заданный интерфейс. За неявность интерфейса тоже нужно выдавать леща.

no-such-file ★★★★★
()
Ответ на: комментарий от Nervous

Твои тонкие чувства никого не волнуют. Интерфейс либо сужен, либо нет, и если в него входила реакция на любое имя метода, то он сужен.

t184256 ★★★★★
()
Ответ на: комментарий от no-such-file

От неявности задания интерфейса никуда не деться, извини. Столько лещей в мире нет, сколько неявных ожиданий таится в интерфейсах.

t184256 ★★★★★
()
Ответ на: комментарий от t184256

Твои тонкие чувства никого не волнуют

А я так надеялся.

Интерфейс либо сужен, либо нет

Чо каво? Я не настоящий оопшник, если что.

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 1)
Ответ на: комментарий от no-such-file

Тогда зачем тебе новый метод?

Чтобы делать что-то, чего базовый класс не может.

Но речь-то ведь идёт о подстановке подкласса в место, где ожидается его родитель. И в этом месте должно ожидаться наличие только методов родителя. Не?

Nervous ★★★★★
()
Ответ на: комментарий от Liz812

Он в корне не прав. Объект от унаследованного класса должен уметь замещать объект от основного класса, всё. Расширяй интейфейс сколько угодно, принципу это не противоречит.

WitcherGeralt ★★
()
Ответ на: комментарий от t184256

От неявности задания интерфейса никуда не деться

Запросто можно деться. Если язык что-то не позволяет задать непосредственно в рабочем коде, то на этот случай есть тесты, которые по сути являются offline контрактами.

no-such-file ★★★★★
()
Ответ на: комментарий от Nervous

Так мы про LSP и математику или про наивность?

Если про наивность, есть куча программных объектов, нарушающих LSP. От символических ссылок в файловой системе до «текстовое поле с кнопкой» как расширение «текстовое поле». И про каждый из них есть описание, чего не надо «по наивности» делать.

С математической точки зрения, ещё и дополнительные поля добавлять нельзя, иначе базовый тип имеет свойство «можно сериализовать в 8 байт», а производный, внезапно, не имеет.

monk ★★★★★
()
Ответ на: комментарий от Nervous

Но речь-то ведь идёт о подстановке подкласса в место, где ожидается его родитель

А как ты собрался отличать родителя от особых подклассов? Вот ты добавил некоторым подклассам новый метод. Как объявить функцию обрабатывающую такие подклассы?

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

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

В смысле? Если у тебя объект А имеет тип Животное, то ты просто не сможешь взывать А.рыть(), так как у типа Животное этого метода нет (будет ошибка компиляции). Если объект А имеет тип Крот (унаследованный от Животное), то А.рыть() вызвать можно, но на других животных это никак не влияет.

нельзя просто любых животных подставлять в любой алгоритм

Если алгоритм требует тип аргумента Животное, то метод «рыть» всё равно вызвать не получится, даже если у каких-то подклассов он и определён.

monk ★★★★★
()
Ответ на: комментарий от no-such-file

Вот ты добавил некоторым подклассам новый метод. Как объявить функцию обрабатывающую такие подклассы?

Она будет принимать аргументом только подкласс с таким методом, а не родителя.

monk ★★★★★
()

Liskov substitution - это такой ООП-шный аналог Дейкстровских «Доводов против оператора GOTO»: беспроигрышный повод для холиваров с участием религиозных фанатиков с обоих сторон, а на деле просто инструмент, пригодный в одних случаях и малопригодный в других (без сравнения частоты возникновения этих случаев).

yyk ★★★★★
()
Ответ на: комментарий от no-such-file

А как ты собрался отличать родителя от особых подклассов?

Зачем мне это делать? С точки зрения вызывающего кода не должно быть никакой разницы, родителя ты передал или потомка. Если ожидается ПлоскаяФигура, можно передать туда ПлоскаяФигура, его потомка Треугольник или потомка потомка РавнобедренныйТреугольник, и наличие лишних методов не должно ничего сломать — потому что вызывающий код может рассчитывать только на методы ПлоскаяФигура, которые у всех его потомков 1) должны быть и 2) должны делать the right thing.

Насколько я понимаю, LSP касается пункта 2.

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 2)
Ответ на: комментарий от no-such-file

Запросто можно деться. Если язык что-то не позволяет задать непосредственно в рабочем коде, то на этот случай есть тесты, которые по сути являются offline контрактами.

Вот считай, что направление взгляда и движения такими тестами и проверялось. Кроме того, для любой используемой системы, фактическими тестами является её использование. Если, например, файловый путь «а/b/../c/..» всегда (до появления символических ссылок) можно было сократить да «a», то символические ссылки нарушают интерфейс.

monk ★★★★★
()
Ответ на: комментарий от WitcherGeralt

должен уметь замещать объект от основного класса

Если в основном классе «млекопитающие» определен метод «бежать», а дельфин бегать не умеет, то нужно строить отдельную базовую иерархию «млекопитающие-не-умеющие-бегать»?

Liz812
() автор топика
Ответ на: комментарий от no-such-file

На все человеческие ожидания тестов тоже не напасешься. А доля тех проектов, где напасешься, пренебрежимо мала.

t184256 ★★★★★
()
Ответ на: комментарий от monk

будет ошибка компиляции

Замечательно, а какая разница?

метод «рыть» всё равно вызвать не получится

Да не получится напрямую, придётся кастовать. О чём и речь.

некоторым подклассам

только подкласс с таким методом

На каждый подкласс пилить метод через перегрузку? Прикручивать шаблон?

Итог - если добавляешь новый метод, то использовать можно только через жопу. Я не понимаю с чем конкретно ты споришь?

no-such-file ★★★★★
()
Ответ на: комментарий от yyk

Нет. Это критерий необходимости/возможности наследования для типизированного ООП. Если тебе где-то надо в алгоритм, обрабатывающий объекты типа А передать объект типа Б, значит тип Б обязан быть наследником типа А. А чтобы он мог быть наследником, должен соблюдаться LSP (то есть он должен быть пригоден не только для конкретного алгоритма, в которые его хочешь запихнуть, а для произвольного алгоритма).

А «Доводы против оператора GOTO» теперь называются «Доводы против наследования». Мол, случаи, когда эта ересь необходима, исключительно редки, поэтому надо её вообще выпилить из языка. Уже есть языки с «ООП без наследования».

monk ★★★★★
()
Ответ на: комментарий от Nervous

Зачем мне это делать?

Затем что ты какой-то тип у функции должен указать. У тебя есть Base, и наследники A,B,C,D. Из них C и D имеют новый метод thatThing(). Нужно написать функцию, которая работает только с C и D. Твои предложения?

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от no-such-file

Да не получится напрямую, придётся кастовать. О чём и речь.

Кастовать в программах, соответствующих LSP нельзя.

На каждый подкласс пилить метод через перегрузку? Прикручивать шаблон?

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

Итог - если добавляешь новый метод, то использовать можно только через жопу.

Добавлять не надо через жопу.

monk ★★★★★
()
Ответ на: комментарий от Nervous

Это хорошо, что не ООПшник. Интерфейс - это не ведро каких-то сигнатур, это набор ожиданий. В интерфейс входит не только то, что класс Document реализует метод void upload(URL) throws IOError, но и, например, то, что всегда выполняется за конечное время, не изменяет состояние самого объекта, и, допустим, как и весь код проекта, следует стандарту безопасности АТАТА2020. Интерфейс - это совокупность ожиданий того, кто использует объект. Формализовать их - великое, обычно несбыточное дело.

t184256 ★★★★★
()
Ответ на: комментарий от Liz812

Или семантика метода «бежать» должна включать в себя возможность, что животное двигаться не будет. Тогда дельфин может «бежать».

отдельную базовую иерархию «млекопитающие-не-умеющие-бегать»

Не так. В классе «млекопитающие» метода «бегать» быть тогда не должно (не все умеют). А у него сделать подкласс «бегающие-млекопитающие» с методом «бегать».

monk ★★★★★
()
Ответ на: комментарий от no-such-file

По идее нет, все нормально. Главное не делать сюрпризов в методах, которые есть в базовом классе.

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

Но это лишь помощь программисту, а не гарантия. Подкласс Dog класса или интерфейса Animal легко может в методе walk() отформатировать вам винт. Это будет нарушение принципа, его идеи. Компилятор от такого не защищает

vertexua ★★★★★
()
Ответ на: комментарий от no-such-file

У тебя есть Base, и наследники A,B,C,D. Из них C и D имеют новый метод thatThing(). Нужно написать функцию, которая работает только с C и D

Пишешь интерфейс с методом thatThing, реализуешь его в C и D, пишешь функцию, принимающую интерфейс. Профит.

Но как это все относится к LSP? %)

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 1)
Ответ на: комментарий от vertexua

Винт? Чтобы нарушить LSP, достаточно унаследовать от собаки хромую собаку, которая ходит, но поскуливая, а потом жаловаться, что собака при ходьбе скулит. Да куда там, достаточно сделать собаку, которая ходит слишком медленно. Если от этого в кроется, скажем, race condition (вторая собака будет съедать весь её корм, и первая, хромая, умрёт с голодухи), то знай - виновато нарушение LSP. От собаки ожидалось, что она идёт жрать за разумное время? Тогда хромая собака - не собака.

t184256 ★★★★★
()
Ответ на: комментарий от Liz812

Разве вся полезность ООП не связана с наследованием?

Есть мнение, что вся полезность ООП связана с полиморфизмом, а наследование ненужно. Но это не точно.

Nervous ★★★★★
()
Ответ на: комментарий от Nervous

Пишешь интерфейс с методом thatThing

Хорошая попытка. Теперь у нас есть не нарушающий LSP ThatThingBase

реализуешь его в C и D, пишешь функцию, принимающую интерфейс

А как из этой функции теперь вызывать thisThing из Base?

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от Liz812

Разве вся полезность ООП не связана с наследованием?

Когда-то вся полезная деятельность программ была связана с конструкцией IF .. GOTO. А потом пришёл Дейкстра.

Так и с ООП. В Go есть ООП и нет наследования. В Rust тоже. Точнее есть только для интерфейсов. В 1С нет наследования (но там утиная типизация, поэтому можно реализовать вручную).

monk ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.