LINUX.ORG.RU
Ответ на: комментарий от no-such-file

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

Конечно же это чушь. Принцип гласит всего лишь что замена объекта базового класса на объект наследуемого не должна ломать программу.

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

А добавлять методы можно сколько угодно.

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

и результат выполнения алгоритма не должен после этого измениться

property provable about

Можно оперировать только корректностью программы и только в той степени, в какой это явно и формально допускает язык программирования. Для C++ это означает подстановку классов/объектов, т.е. одинаковость «интерфейса». Только это в C++ - provable. Можно конечно рассматривать тесты как доказательство каких-то более высокоуровневых свойств, но это уже лежит вне контекста ООП и вообще программирования. Само применение LSP в таком расширенном контексте спорно и никогда не предполагалось.

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

Лучше бы ты думал, прежде чем писать.

Публичный метод вне родительского интерфейса ничего не нарушает. Ибо дёрнуть его можно лишь по ссылке на потомка. А ссылка на потомка не имеет отношения к родительскому интерфейсу.

anonymous
()
Ответ на: комментарий от slovazap

обеспечивающий инвариант a=b - если его использовать там где ожидается прямоугольник что-то, очевидно, сломается

И как же он будет обеспечивать такой инвариант? Сеттеры ширины/высоты обязаны обеспечивать независимость a/b что для квадрата невозможно. Т.о. нужен новый метод, который будет устанавливать a/b одновременно. См.рис.1

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

Это ваше мнение или общепринятое в индустрии/научном сообществе?

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

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

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

Каким образом вы сделали такой вывод?

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

Сеттеры ширины/высоты обязаны обеспечивать независимость a/b что для квадрата невозможно.

То есть сеттеры ширины/высоты для квадрата не могут существовать. Значит наследовать его от прямоугольника нельзя.

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

Для C++ это означает подстановку классов/объектов, т.е. одинаковость «интерфейса».

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

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

Напиши простую функцию нахождения разности

Согласен, с такой реализацией всё будет грустно. Но с более строгой сигнатурой, например, _-_ : ∀ x y -> Σ[ z ] (z + y = x), окажется, что для натуральных чисел вычитание определить нельзя.

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

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

Грубо, разница между N и Z в том, что для Z аксиоматически вводится операция «+» c обратными элементами.

Другими словами, ℤ расширяет ℕ операцией получения обратного элемента (назовём её negate) такой, что ∀ x -> x + negate x = 0. Вполне в духе ООП-шного наследования.

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

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

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

Значит наследовать его от прямоугольника нельзя

Тут не всё так просто. На самом деле, если следовать заветам ООП и все свойства (алгоритмы) будут связаны с объектом, то никаких проблем нет.

Пример. Допустим есть алгоритм удвоения площади прямоугольника, который делает это увеличением ширины в два раза. Очевидно, что для квадрата он будет увеличивать площадь не в два, а в четыре раза. Но если этот алгоритм будет не свободной функцией, а методом объекта, то нет никакой проблемы обеспечить согласованность и для квадрата. Да хоть для эллипса.

Этот пример, кстати, хорошо показывает, что такое явно заданный контракт (интерфейс).

не просто подстановку допустимую компилятором

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

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

Каким образом

Я уже раз десять повторил, почему я так считаю. Сожалею, что моё мнение доставляет тебе дискомфорт. Я понимаю. Но не думаю, что повторение в одиннадцатый раз что-то изменит.

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

который делает это увеличением ширины в два раза

Который делает это увеличением ширины в два раза при неизменной высоте. Для квадрата данная операция невозможна.

Очевидно, что для квадрата он будет увеличивать площадь не в два, а в четыре раза.

Это в том случае, если у квадрата a.x = a.x*2 будет неявно менять a.y. То есть нарушать контракт на сеттер.

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

Вот так вот. То есть, если для переменной «температура в градусах Цельсия» должно быть допустимо (по контракту) значение -500? И количество пальцев на руках — любое число от 0 до 255?

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

нарушать контракт на сеттер

Где у сеттера такой контракт? Любой мутатор (в т.ч. сеттер) принципиально может менять стейт объекта произвольным образом.

должно быть допустимо (по контракту) значение -500? И количество пальцев на руках — любое число от 0 до 255

А что мешает явно сделать интервальный тип? Range<-273.15,MAX_DOUBLE>? Range<0,6>?

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

Нет, чудо, если это setWidth() , то и менять он должен m_width. Это и есть контракт.

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

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

anonymous
()
Ответ на: комментарий от Laz

ℤ расширяет ℕ операцией получения обратного элемента

Нет.

Грубо. Z - это как-бы N с операцией «+», при этом для операции «+» аксиоматически задается существование обратного элемента.

Основное для Z - это операция «+». А существование обратного элемента - это (как-бы следствие) чтобы не было проблем с этой операцией.

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

Любой мутатор (в т.ч. сеттер) принципиально может менять стейт объекта произвольным образом.

Физически — может. Формально — это будет нарушение Лисков. О том и речь.

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

Для C++ это означает подстановку классов/объектов, т.е. одинаковость «интерфейса». Только это в C++ - provable.

Вы просто должны понять, что наследование в C++ - это почти всегда не подстановка по Лисков, и перестать путать две эти вещи. Подстановка по Лисков подразумевает, что ты можешь заменить один тип другим, и логика работы программы никак не изменится. Совсем. Вообще. Ни на писечку. Тогда это подстановка по Лисков. Естественно, в крестах обычно нет смысла наследовать, если не менять поведение типа.

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

Кресты нарушают SOLID, вот уж новость!

Я уже писал о том, что кратко можно переписать SOLID одной фразой: одно действие - одна функция.

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

почти всегда не подстановка по Лисков

обычно нет смысла наследовать

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

Впрочем, меня оба варианта не устраивают ввиду своей вырожденности. Я считаю, что LSP нужно применять в той мере, в какой это возможно сделать в явном виде в конкретном языке. Для c++ это означает, что можно заменить объект одного типа объектом другого типа и во-первых, всё скомпилируется (т.е. компилятор докажет валидность постановки типа), а во-вторых, не потребуется никаких дополнительных телодвижений, вроде кастов, условий и т.п.

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

Я считаю, что LSP нужно применять в той мере

Нет, ты считаешь, что LSP не нужно применять. Почему-то все люди, которые мне встречают и втирают про разные принципы SOLID, на практике приходят к тому, что SOLID вообще не нужен. Судя по всему, единственное практическое применение SOLID - это собеседования.

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

ты считаешь, что LSP не нужно применять

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

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

Затем, чтобы собеседование пройти.

Про SOLID всегда спрашивают.

https://habr.com/ru/post/348286/

https://habr.com/ru/post/273843/

Как я понимаю, в радиотехнических «университетах» (которые сейчас на уровне ПТУ) работают (дорабатывают за копейки) абсолютно профнепригодные нищуки «даватели препы».

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

Лискова

Ой-вей!

Да, _функциональная неграмотность_ у нынешней мОлодежи - просто доставляет.

Это таки есть тётенька (ребецн) с девичьей фамилией (nee) Губерман.

А ребе Натан Лисков (surname Liskov) - это её супруг.

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

Грубо. Z - это как-бы N с операцией «+», при этом для операции «+» аксиоматически задается существование обратного элемента.

У чисел слишком много свойств, поэтому предлагаю так. N с операцией «+» в качестве бинарной ассоциативной операции и «0» в качестве нейтрального элемента является моноидом. Z с той же операцией и тем же нейтральным элементом при этом является группой, так как для любого элемента существует обратный. Любой алгоритм, работающий с моноидом, точно так же будет работать и с группой. В этом смысле группа является подтипом моноида. Однако, Z является надмножеством N (пусть и той же мощности). Те же соображения приводят к тому, что тип «непустая строка с операцией конкатенации» - полугруппа - при добавлении к нему пустой строки даёт над подтип - моноид.

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

N с операцией «+» в качестве бинарной ассоциативной операции и «0» в качестве нейтрального элемента...

N, единица «1» (начальный элемент N) - это нейтральный элемент «0», каждое чётное - это положительное число, каждое нёчетное - отрицательное и определим операцию «+» со всеми необходимым свойствами над этими четно-положительными и нечетно-отрицательными числами. И получаем теже «целые числа», где признаком отрицательных чисел является нечетность (2n+1 - это обратное для 2n).

Поэтому, так же голословно как ты, утверждаю, что Z - это подмножество N. :)

С точки зрения теории множеств, элементы N и Z (числа) без операций над ними никак не отличимы, они равны. Различие между N и Z только из-за операции «+».

anonymous
()
Ответ на: комментарий от Laz

Давай-ка вернёмся от множеств к типам. Допустим

type N = 1 | Succ 1

(можно и с нуля, но это не важно в данном случае)

тогда

type Z = N | 0 | -N

как видишь, тут тип-сумма, это значит, что область значений Z больше, чем N, но при этом имеет ту же структуру, тот же «уровень атомарности», это значит, что функции, определённые на N являются неопределёнными на некоторой части Z (0 и -N в данном случае), т.е. не каждое значение типа Z можно подставить в функцию, определённую на N, т.е. LSP нарушен.

С другой стороны, рассмотрим типы-произведения (кортежи/записи):

type point_1d_on_x  = { x : coord }
type point_2d_on_xy = { x : coord ; y : coord }

абстрагируясь от технических деталей (размещение в памяти и всё такое), видно, что любая функция, ожидающая point_1d_on_x, работающая с координатой X может точно также принять и обработать значение типа point_2d_on_xy, т.к. оно тоже имеет координату X,

т.е. point_2d_on_xy может быть подставлен в любую функцию, ожидающую point_1d_on_x,

т.е. point_2d_on_xy является подтипом point_1d_on_x по LSP.

С такой семантикой записей есть свои нюансы даже на уровне системы типов, без учёта технических деталей (см. “TAPL”, главу про субтипы), поэтому языки, где есть типы структуры/записи/кортежи, её избегают, но можно «сэмулировать» объектами:

class t (x : int) = object
    method x = x
end

class s (x : int) (y : int) = object
    inherit t x
    method y = y
end

let t_to_string t =
    Printf.sprintf "%d" t#x

let _ =
    let t = new t 1 in
    let s = new s 2 3 in
    Printf.printf "t = %s, s(t) = %s\n" (t_to_string t) (t_to_string s)

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

korvin_ ★★★★★
()
Последнее исправление: korvin_ (всего исправлений: 5)
Ответ на: комментарий от anonymous

С точки зрения теории множеств, элементы N и Z (числа) без операций над ними никак не отличимы, они равны. Различие между N и Z только из-за операции «+».

Верно. А в реальности мы работаем с дискретными системами, в которых все данные, типы, алгоритмы и прочее можно свести к натуральным числам. Отметил для себя, что в общем случае переносить рассуждения о типах и отношениях между ними на рассуждения о множествах - бессмысленно, спасибо.

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

Должен признать, TAPL я на должном уровне так и не осилил. Про субтипы сумм и произведений я знаю, но больше на интуитивном уровне. А про иерархию классов типов вообще только из практики. Похоже, пришло время сделать очередной заход. Пока же пришёл к выводу, что рассматривать типы просто как множества при рассуждениях о подтипах бессмысленно.

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

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

Laz ★★★★★
()

Исправление cetjs2, минуту назад (текущая версия) :

c++

доколе дебилы распопяжаются на лоре?

mos ★★☆☆☆
()

Это настолько толсто, что даже у бодипозитивщиков считается болезнью. И я не про конкретный тред, а в про лизку в принципе.

kekelia
()

Не баньте Лизу, она веселая

Shulman
()

Ояебу

И за весь тред никто даже не вспомнил, кажется, что существуют еще требования к тому, как потомки обращаются с предусловиями, постусловиями и инвариантами родителя. ОП, если ты сюда не набрасывать пришел - почитай что-нибудь вроде Stephen Roth - Clean C++.

Makhno
()
Ответ на: Ояебу от Makhno

И за весь тред никто даже не вспомнил, кажется, что существуют еще требования к тому, как потомки обращаются с предусловиями, постусловиями и инвариантами родителя

В подтипах лискова нет никаких родителей и потомков, ты бредишь.

byko3y ★★★★
()
Ответ на: Внезапно от Makhno

А что там есть, скажи на милость?

И снова мы приходим к тому, что люди, якобы применяющие принципы SOLID, просто их никогда не читали в исходном виде. Подстановка по Лисков значит, что если в программе, работающей с абстрактной сущностью T, вместо T можно использовать другую сущность - S, и если поведение программы при этом не меняется, то в рамках этой подстановки S называется подтипом T.

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

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

И снова мы приходим к тому, что люди, якобы применяющие принципы SOLID, просто их никогда не читали в исходном виде.

Как сакральное знание формализованного определения принципов SOLID наизусть поможет проектировать правильно?

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

Я где-то это отрицал?

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

Как сакральное знание формализованного определения принципов SOLID наизусть поможет проектировать правильно?

Никак. Так зачем о нем постоянно говорят?

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

Я где-то это отрицал?

Да, вот здесь:

И за весь тред никто даже не вспомнил, кажется, что существуют еще требования к тому, как потомки обращаются с предусловиями, постусловиями и инвариантами родителя. ОП, если ты сюда не набрасывать пришел - почитай что-нибудь вроде Stephen Roth - Clean C++

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

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

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

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

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

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

А еще я не припомню, чтобы мне платили за чтение изложенного здесь твоего безусловно верного

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

byko3y ★★★★
()

4 страницы не читал, но грубо говоря, надо чтобы экземпляр любого класса-потомка можно было передать в любую функцию, принимающую аргумент типа класс-родитель, и всё должно работать. На практике возможны нюансы навроде, с некоторыми потомками работает медленно и жрёт память, но тем не менее должно в принципе работать.

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

Типа, если утка – это птица, то любое правдивое суждение о птицах применимо и к уткам.

«Зачем это нужно» – потому что иначе нелогично получается.

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

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

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

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

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

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

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

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

Именно, этот принцип и говорит о том, что такие алгоритмы плохие, а хорошие так делать не должны.

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