Принцип подстановки Лисков (LSP из SOLID)
От: igor-booch Россия  
Дата: 24.07.11 13:54
Оценка: :))) :))) :))
http://en.wikipedia.org/wiki/Liskov_substitution_principle

Насколько я понял принцип гласит, что если в программе заменить базовые классы классами наследниками, то ничего не должно сломаться. По-мойму бред. Или я что-то неправильно понял.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Re: Принцип подстановки Лисков (LSP из SOLID)
От: LaptevVV Россия  
Дата: 24.07.11 13:56
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>http://en.wikipedia.org/wiki/Liskov_substitution_principle


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

Это динамически происходит. "На место объекта базового класса МОЖНО подставить объект производного класса". Обратно — нельзя.
Будильник — это часы, но не всякие часы — будильник.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[2]: Принцип подстановки Лисков (LSP из SOLID)
От: igor-booch Россия  
Дата: 24.07.11 14:08
Оценка:
LVV>Это динамически происходит.
Что значит динамически?

LVV>"На место объекта базового класса МОЖНО подставить объект производного класса".

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

LVV>Обратно — нельзя.

с этим я абсолютно согласен

LVV>Будильник — это часы, но не всякие часы — будильник.

с этим я тоже абсолютно согласен
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: Ziaw Россия  
Дата: 24.07.11 14:33
Оценка: 1 (1) +2
Здравствуйте, igor-booch, Вы писали:

LVV>>"На место объекта базового класса МОЖНО подставить объект производного класса".

IB>По-мойму это справедливо если классы наследники только добавляют новые методы. Если в классах наследниках происходит перекрытие методов базового класса, то такая подстановка сломает программу. Например базовый класс принтер, класс наследник цветной лазерный принтер. Метод печатать.

Для того принцип и существует, чтобы замена принтера на цветной не ломала программу. Почему она должна сломаться мне совершенно непонятно.
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: LaptevVV Россия  
Дата: 24.07.11 14:50
Оценка: +1 -2
Здравствуйте, igor-booch, Вы писали:


LVV>>Это динамически происходит.

IB>Что значит динамически?
То есть во время работ ы программы

LVV>>"На место объекта базового класса МОЖНО подставить объект производного класса".

IB>По-мойму это справедливо если классы наследники только добавляют новые методы. Если в классах наследниках происходит перекрытие методов базового класса, то такая подстановка сломает программу. Например базовый класс принтер, класс наследник цветной лазерный принтер. Метод печатать.
Дык это же ТОЛЬКО принцип.А уж как его использовать — дело программиста. Но обратите внимание, ВСЕ ОО-языки этот принцип поддерживают.
В С++ этот принцип работает для открытого наследования.
Собственно, виртуальность — это оно и есть...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re: Принцип подстановки Лисков (LSP из SOLID)
От: skeptic  
Дата: 24.07.11 15:49
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>http://en.wikipedia.org/wiki/Liskov_substitution_principle


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


Понял прочитанное ты правильно, да. А вот с выводами почему-то всё сложно.
С какой стати твоя программа должна ломаться из за замены в логгере,например,сущности отвечающей за вывод в stdout на сущность отвечающую за вывод в файл?
Re: Принцип подстановки Лисков (LSP из SOLID)
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 24.07.11 19:04
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>http://en.wikipedia.org/wiki/Liskov_substitution_principle


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


Правильно понял, но это справедливо только при условии, что наследование (inheritance) типов в данной программной системе действительно выражает отношение "тип-подтип" в том смысле, который подразумевает LSP. Обычно полагают, что так оно и есть, но в отдельных случаях это правило может нарушаться.

Говоря другими словами, в программе ничего не должно сломаться при замене базовых классов классами-наследниками, если и только если классы спроектированы с учётом LSP.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 24.07.11 19:16
Оценка: +3
Здравствуйте, igor-booch, Вы писали:

LVV>>"На место объекта базового класса МОЖНО подставить объект производного класса".

IB>По-мойму это справедливо если классы наследники только добавляют новые методы. Если в классах наследниках происходит перекрытие методов базового класса, то такая подстановка сломает программу. Например базовый класс принтер, класс наследник цветной лазерный принтер. Метод печатать.

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

Собственно, LSP не случайно сформулирован, как "свойство q(x)...". Суть в том, что набор соглашений, справедливых для базового типа должен соблюдаться и для типа-наследника (если они LSP-compliant). Говорим мы при этом об интерфейсных методах, или, скажем, о побочных эффектах в виде генерации какого-то события с заданной периодичностью — не суть важно.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[4]: Принцип подстановки Лисков (LSP из SOLID)
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 24.07.11 19:19
Оценка: 1 (1) +1
Здравствуйте, LaptevVV, Вы писали:

LVV>Дык это же ТОЛЬКО принцип.А уж как его использовать — дело программиста. Но обратите внимание, ВСЕ ОО-языки этот принцип поддерживают.

LVV>В С++ этот принцип работает для открытого наследования.
LVV>Собственно, виртуальность — это оно и есть...

Точнее — на C++ можно реализовать LSP с помощью открытого наследования и виртуальных методов.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[2]: Принцип подстановки Лисков (LSP из SOLID)
От: igor-booch Россия  
Дата: 24.07.11 19:50
Оценка: :)
Предположим у нас есть класс SimpleLogger, у него виртуальный метод Log(string message), который просто выводит сообщения на консоль.
Есть класс DbLogger, который наследуется от SimpleLogger. В DbLogger перекрыт метод Log(string message) и добавлена функциональность логирования сообщения в БД.
Теперь меняем в программе SimpleLogger на DbLogger, опаньки, а баз данных то нет, комп от сети отключен, даже connection string нигде не указан. Логирование не работает.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: skeptic  
Дата: 24.07.11 20:27
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>Предположим у нас есть класс SimpleLogger, у него виртуальный метод Log(string message), который просто выводит сообщения на консоль.

IB>Есть класс DbLogger, который наследуется от SimpleLogger. В DbLogger перекрыт метод Log(string message) и добавлена функциональность логирования сообщения в БД.
IB>Теперь меняем в программе SimpleLogger на DbLogger, опаньки, а баз данных то нет, комп от сети отключен, даже connection string нигде не указан. Логирование не работает.


Теперь только объясни себе какой смысл имеет логгирование в базу там где этой базы нет? Может если базы нет то объект который в неё захочет писать должен так и сказать мол базы насяйника нет, а не крушить всю систему из за этого? Ну а кто то вышестоящий увидев такую пьянку может попытаться заменить этот ваш DbLogger на что то более подходящее, что гораздо проще будет если иерархия построена на LSP. И это нормальное поведение, где вы тут нарушение LSP увидели я не понимаю.
LSP относится к дизайну системы, он как бы говорит нам — "Поразмысли над тем что бы спроектировать систему вот так и вероятно в последствии не будет так мучительно больно за бесцельно потраченные человеко-часы саппорта."
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: hardcase Пират http://nemerle.org
Дата: 24.07.11 20:43
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>Предположим у нас есть класс SimpleLogger, у него виртуальный метод Log(string message), который просто выводит сообщения на консоль.

IB>Есть класс DbLogger, который наследуется от SimpleLogger. В DbLogger перекрыт метод Log(string message) и добавлена функциональность логирования сообщения в БД.

А почему DbLogger должен быть наследником SimpleLogger?
/* иЗвиНите зА неРовнЫй поЧерК */
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 24.07.11 20:48
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>Предположим у нас есть класс SimpleLogger, у него виртуальный метод Log(string message), который просто выводит сообщения на консоль.

IB>Есть класс DbLogger, который наследуется от SimpleLogger. В DbLogger перекрыт метод Log(string message) и добавлена функциональность логирования сообщения в БД.
IB>Теперь меняем в программе SimpleLogger на DbLogger, опаньки, а баз данных то нет, комп от сети отключен, даже connection string нигде не указан. Логирование не работает.

Это означает, что отношение типов SimpleLogger-DbLogger не является отношением "тип-подтип" в том смысле, который подразумевает LSP. По идее, для полного LSP-compliancy где-то отдельно должны упаковываться "настроечные параметры", специфичные для каждого класса.

Ну или как вариант, придётся сократить область соблюдения LSP только до интерфейса собственно логгирования (метода Log(string) ), а остальные параметры классов будут использоваться отдельно, вне контекста, в котором должен соблюдаться LSP. Простейший пример такого компромисса — "фабричный метод", когда специфика конструкторов разных классов упоминается только внутри самого фабричного метода, а остальные потребители работают с созданными экземплярами одинаково. Отсюда, как ты понимаешь, мы приходим к концепции "интерфейса" (interface), как к явному декларированию набора свойств, которые должны быть реализованы всеми классами, реализующими соответствующий интерфейс (сравни с формулировкой LSP).
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re: Принцип подстановки Лисков (LSP из SOLID)
От: z00n  
Дата: 25.07.11 03:53
Оценка: 2 (1)
Здравствуйте, igor-booch, Вы писали:

IB>http://en.wikipedia.org/wiki/Liskov_substitution_principle


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


Это еще что — попробуйте поразмыслить об контравариантности аргументов функций
http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)

A call to a function with a parameter of type T (defined as fun f (x : T) : Integer) can be replaced by a call to a function g (defined as fun g (x : S) : Integer) if T ≤ S. In other words, if g cares less about the type of its parameter, then it can replace f anywhere, since both return an Integer. So, in a language accepting function arguments, g ≤ f and the type of the parameter to f is said to be contravariant.

Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: minorlogic Украина  
Дата: 25.07.11 06:07
Оценка: +3 -1
Здравствуйте, igor-booch, Вы писали:

IB>Предположим у нас есть класс SimpleLogger, у него виртуальный метод Log(string message), который просто выводит сообщения на консоль.

IB>Есть класс DbLogger, который наследуется от SimpleLogger. В DbLogger перекрыт метод Log(string message) и добавлена функциональность логирования сообщения в БД.
IB>Теперь меняем в программе SimpleLogger на DbLogger, опаньки, а баз данных то нет, комп от сети отключен, даже connection string нигде не указан. Логирование не работает.

Кривой пример. Для консольного логера тоже могут быть проблемы , нет консоли , нет райнайм библиотеки, не хватает памяти для логирования и т.п.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Ищу работу, 3D, SLAM, computer graphics/vision.
Re[5]: Принцип подстановки Лисков (LSP из SOLID)
От: jazzer Россия Skype: enerjazzer
Дата: 25.07.11 06:50
Оценка: +2 :)
Здравствуйте, Геннадий Васильев, Вы писали:

ГВ>Здравствуйте, LaptevVV, Вы писали:


LVV>>Дык это же ТОЛЬКО принцип.А уж как его использовать — дело программиста. Но обратите внимание, ВСЕ ОО-языки этот принцип поддерживают.

LVV>>В С++ этот принцип работает для открытого наследования.
LVV>>Собственно, виртуальность — это оно и есть...

ГВ>Точнее — на C++ можно реализовать LSP с помощью открытого наследования и виртуальных методов.


А также шаблонов
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: Принцип подстановки Лисков (LSP из SOLID)
От: MasterZiv СССР  
Дата: 25.07.11 06:51
Оценка:
On 24.07.2011 17:54, igor-booch wrote:

> Насколько я понял принцип гласит, что если в программе заменить базовые классы

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

Ты неправильно понял.
Posted via RSDN NNTP Server 2.1 beta
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: jazzer Россия Skype: enerjazzer
Дата: 25.07.11 06:52
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>Предположим у нас есть класс SimpleLogger, у него виртуальный метод Log(string message), который просто выводит сообщения на консоль.

IB>Есть класс DbLogger, который наследуется от SimpleLogger. В DbLogger перекрыт метод Log(string message) и добавлена функциональность логирования сообщения в БД.
IB>Теперь меняем в программе SimpleLogger на DbLogger, опаньки, а баз данных то нет, комп от сети отключен, даже connection string нигде не указан. Логирование не работает.

Это всего лишь значит, что у тебя LSP не соблюдается — ССЗБ
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: MasterZiv СССР  
Дата: 25.07.11 07:01
Оценка: +1
On 24.07.2011 18:08, igor-booch wrote:

> LVV>"На место объекта базового класса МОЖНО подставить объект производного класса".

> По-мойму это справедливо если классы наследники только добавляют новые методы.
> Если в классах наследниках происходит перекрытие методов базового класса, то
> такая подстановка сломает программу. Например базовый класс *принтер*, класс
> наследник*цветной лазерный принтер*. Метод *печатать*.

Это -- принцип "как оно должно быть", а не принцип "как оно есть на самом деле
в неработающих программах".

Открытое наследование в ООП (в большинстве языков программирования)
позволяет тебе использовать принцип подстановки. Т.е. подставить
вместо объекта базового класса объект-наследник. Наличие этого
принципа даёт тебе гарантию, что программа твоя соберётся
(скомпилируется, загрузится и т.п.), т.е. язык программирования
тебе ДАСТ ЭТО СДЕЛАТЬ. Но что это будет работать естественно
никто не гарантирует. Это ДОЛЖНО работать в хорошо спроектированной
и закодированной программе, иначе в ООП просто нет никакого смысла.

Кстати, есть разные языки относительно соблюдения этого
принципа. Есть "статические" языки, где проверка типа происходит
на этапе компиляции программы (сборки). Там этот принцип
соблюдается относительно всего класса (предка и наследника)
целиком. Есть "динамические" языки, где на этапе сборки
программы конкретный тип не проверяется, а проверка эта
делается только во время работы на этапе посылки объекту
какого-то класса определённого сообщения (вызова метода то есть).
В таких языках класс-"наследник" даже может быть формально
не унаследованным от своего класса-"предка", и там уже речь
идёт о "соблюдении протоколов".
Posted via RSDN NNTP Server 2.1 beta
Re: Принцип подстановки Лисков (LSP из SOLID)
От: jazzer Россия Skype: enerjazzer
Дата: 25.07.11 07:35
Оценка: 37 (2) +2
Здравствуйте, igor-booch, Вы писали:

IB>http://en.wikipedia.org/wiki/Liskov_substitution_principle


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


Это признак корректности спроектированной системы типов и отношений между ними. Если он не соблюдается — ССЗБ.
Он должен соблюдаться, иначе ты получаешь кривую программу.

Фактически, это даже не принцип в том смысле, что "так бы делать хорошо".
LSP является определением отношения тип-подтип.

Другое дело — когда у тебя есть какие-то конструкции в языке, которые позволяют номинально это отношение выразить, даже если формально оно нарушено.
Тогда ты можешь выразить отношение тип-подтип, просто использовав сооответствующую синтаксическую конструкцию для выражения наследования в твоем языке (class Derived: public Base), но это не сделает их настоящей парой тип-подтип, пока ты не приложишь специальные усилия, чтобы удовлетворить LSP.

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

Естественно, никто не запретит тебе какой-то принцип нарушить, но вот только ты уже себе сам будешь ЗБ, когда твоя программа начнет падать, вести себя странно, или просто с большим трудом поддаваться поддержке и изменениям, провоцируя ошибки на каждом шагу.
Каждая пара классов, нарушающая LSP — это подводные грабли.
Это же относится и к остальным принципам SOLID.
К примеру — никто не запрещает тебе нарушить Single responsibility principle и написать универсальный всемогутер — только, я думаю, ты сам знаешь, каким геморроем оборачивается поддержка такого монстра.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[4]: Принцип подстановки Лисков (LSP из SOLID)
От: SV.  
Дата: 25.07.11 15:46
Оценка:
Здравствуйте, Геннадий Васильев, Вы писали:

ГВ>Угу-угу, это как раз и есть источник сложностей ОО-проектирования. Вот и спроектируй программу так, чтобы замена класса MatrixPrinter на ColorPrinter проходила незаметно для использующего кода.


В чем проблема? Если забыть о том, что различие между матричным и цветным лазерном принтере лежит на уровне драйвера, а не приложения, вот код, который печатает одну и ту же картинку на всех принтерах, подключенных к компьютеру:

abstract class Printer
{
    abstract void Print(Image image);
    abstract void Print(PostScript file);
}

class ColorLaserPrinter : Printer
{
    override void Print(Image image)
    {
        //...
    }

    override void Print(PostScript file);
    {
        //...
    }
}

class MatrixPrinter : Printer
{
    override void Print(Image image)
    {
        //...
    }

    override void Print(PostScript file);
    {
        //...
    }
}

class Environment
{
...
    Printer[] EnumAvailablePrinters() { ... }
...
}

class MyApp
{
    void main()
    {
        var image = new Image("/images/image.png");
        foreach (var printer in printers)
            printer.Print(image);
    }
}
Re[4]: Принцип подстановки Лисков (LSP из SOLID)
От: vdimas Россия  
Дата: 25.07.11 15:55
Оценка:
Здравствуйте, minorlogic, Вы писали:

M>Кривой пример. Для консольного логера тоже могут быть проблемы , нет консоли , нет райнайм библиотеки, не хватает памяти для логирования и т.п.


Это будут ошибки окружения, а не программы. Компьютер вообще сломаться может не вовремя, и что теперь?
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: SV.  
Дата: 25.07.11 15:56
Оценка: +1
Здравствуйте, igor-booch, Вы писали:

IB>Теперь меняем в программе SimpleLogger на DbLogger


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

Или не рассматривайте конструктор как часть публичного интерфейса. То же самое, но без геморроя. Тогда вы сможете заменить SimpleLogger на DbLogger при использовании (как некого Logger'а), но не при инстанцировании.
Re[5]: Принцип подстановки Лисков (LSP из SOLID)
От: minorlogic Украина  
Дата: 25.07.11 15:59
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Это будут ошибки окружения, а не программы. Компьютер вообще сломаться может не вовремя, и что теперь?


Это как то связанно с моим постом? Или темой топика?
Ищу работу, 3D, SLAM, computer graphics/vision.
Re[4]: Принцип подстановки Лисков (LSP из SOLID)
От: Ziaw Россия  
Дата: 25.07.11 16:44
Оценка:
Здравствуйте, SV., Вы писали:

SV.>LSP затрагивает публичный интерфейс, если я правильно помню.


LSP затрагивает не столько публичный интерфейс иерархии классов, сколько внешние методы с ней работающие. Именно они должны работать корректно с любым потомком. Если метод принимает на вход Printer, он должен точно так же корректно работать если ему передали ColorPrinter или MatrixPrinter.
Re[2]: Принцип подстановки Лисков (LSP из SOLID)
От: igor-booch Россия  
Дата: 25.07.11 19:30
Оценка:
MZ>Ты неправильно понял.

А как правильно понимать этот принцип?
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Re[2]: Принцип подстановки Лисков (LSP из SOLID)
От: igor-booch Россия  
Дата: 25.07.11 19:33
Оценка:
ГВ>Правильно понял, но это справедливо только при условии, что наследование (inheritance) типов в данной программной системе действительно выражает отношение "тип-подтип" в том смысле, который подразумевает LSP. Обычно полагают, что так оно и есть, но в отдельных случаях это правило может нарушаться.

Приведите пожалуйста примеры неправильного и правильного наследования сточки зрения LSP.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 26.07.11 01:07
Оценка: 160 (9)
Здравствуйте, igor-booch, Вы писали:

ГВ>>Правильно понял, но это справедливо только при условии, что наследование (inheritance) типов в данной программной системе действительно выражает отношение "тип-подтип" в том смысле, который подразумевает LSP. Обычно полагают, что так оно и есть, но в отдельных случаях это правило может нарушаться.


IB>Приведите пожалуйста примеры неправильного и правильного наследования сточки зрения LSP.


С точки зрения LSP нет никакого "наследования". Есть отношение "тип-подтип", а воплощено оно наследованием или нет — не имеет никакого значения. Главное — сформулировать набор требований к соответствующим сущностям. То есть прямо сформулировать, а не надеяться на "само собой получится, если мы правильно постигнем дзен". Не получится, не надейтесь.

Итак, записываем набор требований к сигнатурам и поведению соответствующих типов. Для примера возьмём набившую оскомину задачу рисования кругов и прочих треугольников. Обобщать мы их будем в виде некоторых графических примитивов, которые обозначим как сущность Shape.

Что должен уметь графический примитив? В общем-то, ничего, если не определён контекст его использования. Значит, мы должны разобраться с контекстом. Итак, в контексте:

— Некоторое "Графическое пространство" (Canvas);
— Модель представления координат (X-Y);
— Цветовое пространство (Palette, Color);
— "Микропримитивная" операция: putPixel(Canvas, Color, Coordinate x, Coordinate y).

Вот теперь, в этом наборе символов начинаем игру в графические примитивы. Чего мы хотим от графического примитива? Допустим, что мы хотим, чтобы он умел себя нарисовать в заданной базовой точке на заданном холсте. Получаем первую формальную запись требований к такому объекту (здесь и далее — псевдокод):

void draw(Shape shape, Canvas canvas, Coordinate X, Coordinate Y);


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

void setPalette(Shape shape, Palette palette);
Palette getPalette(Shape shape);


Прикидываем одно место к носу и замечаем, что язык у нас объектно-ориентированный, значит, общий для всех параметр shape можно вынести в "класс", то есть:

class Shape {
void draw(Canvas canvas, Coordinate X, Coordinate Y);
void setPalette(Palette palette);
Palette getPalette();
}


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

class Shape {
  void setCanvas(Canvas canvas); // Выносим отдельно управление Canvas
  Canvas getCanvas(); // Должно выполняться условие: s.setCanvas(c); s.getCanvas() == c; Что положили, то и вытащили.

  void setBasePoint(Coordinate X, Coordinate Y);
  Coordinate getBasePointX();
  Coordinate getBasePointY();

  void draw(); // draw растерял все свои аргументы

  void setPalette(Palette palette);
  Palette getPalette();
}


Получив такой интерфейс, мы можем заняться разработкой системы рисования, не имея ещё ни одного примитива. Опускаю промежуточные рассуждения, сразу приведу примерно то, что получается:

class Canvas {
  void addShape(Shape);
  void drawAll(); // Рисуем всё, что накопилось
  
  void beginDraw();
  void endDraw();
  void putPixel(Color c, Coordinate X, Coordinate Y);
private:
  Shape m_allShapes[]; // Не суть важно, как именно выглядит этот массив. Главное - что это одномерный массив.
}


Собственно, дальше пирожки врозь — детализация Canvas будет проводиться отдельно, а детализация Shape — отдельно. Отмечу один момент: Canvas содержит два метода — beginDraw и endDraw. Эти два метода должны вызываться "фигурой" в начале и конце цикла рисования соответственно. Соответствующее требование вносится в список требований Shape:

class Shape {
  ...
  // requires: Canvas.beginDraw / Canvas.endDraw call.
  void draw();
  ...
}


Пока что требование записано неформально. Чтобы привести его к формализованному виду, создадим некий базовый класс:

class BasicShape : public Shape {
  void setCanvas(Canvas canvas) { m_canvas = canvas; }
  Canvas getCanvas() { return m_canvas; }

  // Аналогично воплощаем здесь управление палитрой и базовыми точками.


  void draw() {
    m_canvas.beginDraw();
    drawPixels();
    m_canvas.endDraw();
  }

  void drawPixels(); // Этот метод предназначен для перекрытия классами-наследниками BasicShape,
                     // Canvas про него ничего не знает

protected:
  Canvas m_canvas;
}


Теперь можно переходить к кругам-треугольникам и прочим примитивам:

class Bar : public BasicShape { ... };
class Circle : public BasicShape { ... };
class Triangle : public BasicShape { ... };
class Polygon : public BasicShape { ... };

class ShapeAdapter : public Shape { ... };

class SomethingSmart {
  Shape anchorShape() { return m_shapeAdapter; } // Можно и так сделать
protected:
  ShapeAdapter m_shapeAdapter; // Этот объект каким-то загадочным способом связывает интерфейс Shape и методы класса SomethingSmart
};


Как ты понимаешь, нам совершенно не важно, как именно технически связан интерфейс Shape с его реализацией. Наследование, агрегация, ещё как-нибудь. Важно, чтобы поведение реализации соответствовало требованиям, предъявляемым к Shape. Тогда мы автоматически удовлетворяем Liskov Substitution Principle, соответствующие классы находятся в отношении "тип-подтип" (т.е. — являются подтипами типа Shape) и у нас не возникает противоречий при чтении инструкции:

class Bar : public BasicShape { ... };


Здесь наследование реализует отношение "тип-подтип" в LSP-смысле этого слова. Сие интуитивно понятно и не вызывает вопросов.

Сложности начнутся, если кому-то стукнет в голову, например, "сэкономить" на наследовании от BasicShape, вместо этого перенеся соответствующий код в Canvas:

class CrazyShape : public Shape { // Наследование очень похоже на предыдущее...
  void draw() {
    m_canvas.putPixel(0, 1, 1); // ...но только внешне.
  }
}

// В Canvas придётся добавить такую фишку:
class Canvas {
  void drawAll() {
    ...
    if (typeOf(someShape) == CrazyShape) {
      // Для этого идиота придётся делать исключение
      beginDraw();
      someShape.draw();
      endDraw();
    } else {
      // Остальные - в норме
      someShape.draw();
    }
  }
}


В данном случае формальная часть, соблюдение которой требуется компилятором, вполне удовлетворена: новый класс унаследован от интерфейса Shape и экземпляр CrazyShape может быть подставлен туда, где должен использоваться Shape. А вот остальная часть контракта, которая была реализована в BasicShape — нарушена. Вместе с ней нарушен LSP, поскольку новый класс (внимание!) не обладает свойством: "вызывает пару beginDraw()/endDraw()". То есть не смотря на "правильное" наследование класс CrazyShape не является LSP-compliant подтипом класса Shape.

Это нюанс, о котором часто забывают в пылу споров о Глубоком Смысле того или иного сугубо синтаксического приёма. Например, часто ломают копья по вопросу о том, является ли public- (protected, private) -наследование реализацией отношения "тип-подтип" или нет. Проблема состоит в том, что синтаксическая конструкция "наследование" не является сама по себе ничем таким, что позволяло бы сделать вывод о более общих характеристиках наследуемых классов. LSP-compliant отношение "тип-подтип" можно как соблюсти с использованием private-наследования, так и нарушить при public-наследовании. То есть подобные споры, в общем-то, есть споры ни о чём.

Возвращаясь к твоему вопросу: с точки зрения LSP не бывает правильного и неправильного наследования, поскольку LSP определён на более общих характеристиках, нежели наследование. Единственная причина, по которой в современных языках программирования иной раз необходимо пользоваться наследованием для соблюдения LSP состоит в том, что компилятор разрешит подстановку класса B на место класса A только в том случае, если B является наследником A. Но повторюсь, само себе это никак не гарантирует соблюдения LSP, то есть наследование — это условие нередко необходимое, но отнюдь не достаточное. Например типы, используемые в качестве аргументов шаблона могут просто содержать методы с нужными сигнатурами, при этом наследниками какого-то типа они быть вовсе не обязаны. И с другой стороны, помним о таком приёме:

class ShapeAdapter : public Shape { ... };

class SomethingSmart {
  Shape anchorShape() { return m_shapeAdapter; } // Можно и так сделать
protected:
  ShapeAdapter m_shapeAdapter; // Этот объект каким-то загадочным способом связывает интерфейс Shape и методы класса SomethingSmart
};


Здесь SomethingSmart не унаследован прямо от Shape, но тем не менее, посредством m_shapeAdapter и метода anchorShape() он может быть использован там, где ожидается Shape. Отличие такой агрегации от наследования становится пренебрежимо малым, если учесть, что создание класса часто упаковывается в "фабрику":

Shape createShape(string typeName) {
  if (typeName == "SomethingSmart") {
    SomethingSmart obj = new SomethingSmart(...);
    return obj.anchorShape();
  }
  ...
}


Так что, наследование — это только один из возможных приёмов.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: MasterZiv СССР  
Дата: 26.07.11 06:00
Оценка:
On 25.07.2011 23:30, igor-booch wrote:

> MZ>Ты неправильно понял.

>
> А как правильно понимать этот принцип?

А я уже написал.
Posted via RSDN NNTP Server 2.1 beta
Re[4]: Принцип подстановки Лисков (LSP из SOLID)
От: igor-booch Россия  
Дата: 26.07.11 06:32
Оценка:
Спасибо за содержательный ответ! Буду его изучать.

Может посоветуете какую-нибудь хорошую книгу по теме?
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Re[5]: Принцип подстановки Лисков (LSP из SOLID)
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 26.07.11 10:56
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>Спасибо за содержательный ответ! Буду его изучать.




IB>Может посоветуете какую-нибудь хорошую книгу по теме?


По LSP-то? На него много где ссылаются в литературе по проектированию ПО, в общем, это основополагающий принцип проектирования иерархий классов. Неплохой стартовый набор ссылок можно найти в английской версии Вики. Ну или вот, например, достаточно простая статья на Object Mentor.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re: Принцип подстановки Лисков (LSP из SOLID)
От: Wolverrum Ниоткуда  
Дата: 26.07.11 17:37
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>http://en.wikipedia.org/wiki/Liskov_substitution_principle


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


Вы, когда наследуетесь от предка, специализируете поведение будущего объекта или, наоборот, обобщаете?
Re[6]: Принцип подстановки Лисков (LSP из SOLID)
От: vdimas Россия  
Дата: 27.07.11 10:16
Оценка:
Здравствуйте, minorlogic, Вы писали:

V>>Это будут ошибки окружения, а не программы.


M>Это как то связанно с моим постом? Или темой топика?


Прочитай еще раз свой пост и помедитируй.
Re[5]: Принцип подстановки Лисков (LSP из SOLID)
От: vdimas Россия  
Дата: 27.07.11 10:19
Оценка: +2
Здравствуйте, Ziaw, Вы писали:

Z>LSP затрагивает не столько публичный интерфейс иерархии классов, сколько внешние методы с ней работающие.


LSP затрагивает как саму иерархию, так и код, её использующий. Даже идеальная иерархия типов, разработанная для соблюдения LSP, может быть использована с его нарушением.
Re[6]: Принцип подстановки Лисков (LSP из SOLID)
От: artelk  
Дата: 27.07.11 11:46
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Здравствуйте, Ziaw, Вы писали:


Z>>LSP затрагивает не столько публичный интерфейс иерархии классов, сколько внешние методы с ней работающие.


V>LSP затрагивает как саму иерархию, так и код, её использующий. Даже идеальная иерархия типов, разработанная для соблюдения LSP, может быть использована с его нарушением.


А пример приведи, плиз.
Re[6]: Принцип подстановки Лисков (LSP из SOLID)
От: Sinclair Россия https://github.com/evilguest/
Дата: 27.07.11 17:22
Оценка: +1
Здравствуйте, vdimas, Вы писали:

V>LSP затрагивает как саму иерархию, так и код, её использующий. Даже идеальная иерархия типов, разработанная для соблюдения LSP, может быть использована с его нарушением.

Всё упирается в то, что мы считаем контрактом.
С точки зрения С++, контракт исчерпывается типами и количеством аргументов и возвращаемого результата.
Именно это проверяет компилятор.
То, что метод Numeral.Increment имеет ту же семантику (в общем смысле), что и метод Integer.Increment, компилятор проверить не может.
Но это — проблемы компилятора и конкретного языка, а не принципа в целом.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[7]: Принцип подстановки Лисков (LSP из SOLID)
От: Ziaw Россия  
Дата: 28.07.11 17:13
Оценка:
Здравствуйте, artelk, Вы писали:

A>А пример приведи, плиз.



if (x.GetType() == typeof(SomeConcreteType))
{
  //...
}
Re[5]: Принцип подстановки Лисков (LSP из SOLID)
От: SV.  
Дата: 29.07.11 10:26
Оценка: +1
Здравствуйте, Ziaw, Вы писали:

SV.>>LSP затрагивает публичный интерфейс, если я правильно помню.


Z>LSP затрагивает не столько публичный интерфейс иерархии классов, сколько внешние методы с ней работающие. Именно они должны работать корректно с любым потомком. Если метод принимает на вход Printer, он должен точно так же корректно работать если ему передали ColorPrinter или MatrixPrinter.


Ослу понятно, что если конструкторы будут различаться (требовать разных инициализационных данных — connection string / console), то и методы, которые инстанцируют логгеры, тоже будут различаться.

Вопрос в чистом виде такой: классы, которые дают возможность одинаково себя использовать, но требуют по-разному порождать — удовлетворяют LSP или нет? Я для себя ответил так: It's OK.

Пример с логгерами приведен. Другой пример: простенький векторный редактор. Вы щелкаете по кнопке "Рисую прямоугольник". Window [Controller + View] запоминает режим и в соответствии с ним показывает вам рамку после первого щелчка. После отпускания кнопки мыши отрабатывает switch, и в класс Document [Model] уходит команда "Новый прямоугольник". Тот инстанцирует Rect, добавляет его в коллекцию фигур, и только после этого разница между ним и каким-нибудь кругом исчезает.

Если есть возражения по сути, welcome.
Re[2]: Принцип подстановки Лисков (LSP из SOLID)
От: igor-booch Россия  
Дата: 29.07.11 15:01
Оценка:
W>Вы, когда наследуетесь от предка, специализируете поведение будущего объекта или, наоборот, обобщаете?

специализирую
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Re: Принцип подстановки Лисков (LSP из SOLID)
От: LaptevVV Россия  
Дата: 29.07.11 15:17
Оценка: :)
Здравствуйте, igor-booch, Вы писали:

Кстати о "правильности" наследования:

Тимоти Бадд [27] приводит интересную классификацию форм наследования. Форма наследования определяет — для чего, с какой целью используется наследование. Бадд считает, что порождение дочернего класса может быть выполнено по следующим причинам:
 специализация. Класс-наследник является специализированной формой родительского класса — в наследнике просто переопределяются методы. Принцип подстановки выполняется. Очевидно, что такая форма наследования в С++ реализуется простым открытым наследованием.
Примером является наследование часы -> будильник;
 спецификация. Дочерний класс реализует поведение, описанное в родительском классе. Ясно, что в С++ эта форма реализуется простым открытым наследованием от абстрактного класса;
 конструирование. Класс-наследник использует методы базового класса, но не является его подтипом (принцип подстановки не выполняется). В С++ такую форму можно реализовать простым закрытым наследованием;
 расширение. В класс-потомок добавляют новые методы, расширяя поведение родительского класса; принцип подстановки в такой форме выполняется;
 обобщение. Дочерний класс обобщает поведение базового класса. Обычно такое наследование используется в тех случаях, когда мы не можем изменить поведение базового класса (например, базовый класс является библиотечным классом);
 ограничение. Класс-наследник ограничивает поведение родительского класса. Очевидно, что в С++ такой вид наследования реализуется простым закрытым наследованием (пример — TUniversalDeque -> TStack );
 варьирование. Базовый класс и класс-потомок являются вариациями на одну тему, однако связь «класс-подкласс» произвольна, например, «квадрат-прямоугольник» или «прямоугольник-квадрат». Эта форма фактически не отличается от «конструирования», так как класс-наследник, очевидно, «использует методы базового класса, но не является его подтипом»;
 комбинирование. Дочерний класс наследует черты нескольких классов — это множественное наследование.

Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[6]: Принцип подстановки Лисков (LSP из SOLID)
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 29.07.11 22:20
Оценка:
Здравствуйте, SV., Вы писали:

Z>>LSP затрагивает не столько публичный интерфейс иерархии классов, сколько внешние методы с ней работающие. Именно они должны работать корректно с любым потомком. Если метод принимает на вход Printer, он должен точно так же корректно работать если ему передали ColorPrinter или MatrixPrinter.


SV.>Ослу понятно, что если конструкторы будут различаться (требовать разных инициализационных данных — connection string / console), то и методы, которые инстанцируют логгеры, тоже будут различаться.


SV.>Вопрос в чистом виде такой: классы, которые дают возможность одинаково себя использовать, но требуют по-разному порождать — удовлетворяют LSP или нет? Я для себя ответил так: It's OK.


Не совсем правильная постановка вопроса, потому что ты здесь упускаешь из виду, что LSP формулируется относительно некоторого базового типа, соответственно, классы могут удовлетворять LSP тоже только по отношению к какому-то базовому типу. Поясню на примере.

class Shape {
public:
  virtual void draw() = 0;
  virtual void setCanvas(Canvas *) = 0;
};

class Circle : public Shape {
public:
  Circle(Point center, int radius);
  void draw();
  void setCanvas(Canvas *);
};

class Line : public Shape {
public:
  Line(Point begin, Point end);
  void draw();
  void setCanvas(Canvas *);
};


Предположим для ясности, что реализации draw и setCanvas соответствуют всем соглашениям и т.п. Так вот, базовый тип (Shape) не содержит определения конструктора, следовательно способ порождения его наследников не должен учитываться при оценке LSP-compliancy. Собственно, наследники могут вводить ещё туеву хучу своих собственных методов по необходимости, но если соглашения Shape при этом не нарушаются, то иерархия остаётся LSP-compliant для базового Shape.

Конструкторы могут быть включены в оценку LSP-compliancy только в том случае, если язык программирования допускает наследование конструкторов и при использовании требуется определённая сигнатура конструктора. Иначе они всегда будут относиться к одному-единственному типу и вопрос о об LSP-compliant наследовании не стоит. Тут ещё есть такой парадокс, что имя конструктора нередко должно совпадать с именем типа, а значит, в точке использования конструктора почти всегда будет указан конкретный тип и вопрос о наследовании снова отпадает сам собой. Но если языки или технология позволяют выбрать тип по содержимому переменной (GUID, строковое имя, ссылка на метакласс...) и потом вызвать конструктор некоторым унифицированным образом — тут да, конструктор может быть включён в оценку LSP-compliancy. Вот такая вот путаница.

Кстати, если внимательно прочесть твой вопрос:

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

...то в нём можно найти скрытое противоречие: "одинаково использовать" vs. "по-разному порождать". Из этого противоречия следует, что порождение находится за пределами "использования" и в оценке LSP-compliancy, таким образом, автоматически не участвует. Так что, да, It's OK. Definitely.

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

SV.>Пример с логгерами приведен. Другой пример: простенький векторный редактор. Вы щелкаете по кнопке "Рисую прямоугольник". Window [Controller + View] запоминает режим и в соответствии с ним показывает вам рамку после первого щелчка. После отпускания кнопки мыши отрабатывает switch, и в класс Document [Model] уходит команда "Новый прямоугольник". Тот инстанцирует Rect, добавляет его в коллекцию фигур, и только после этого разница между ним и каким-нибудь кругом исчезает.


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

SV.>Если есть возражения по сути, welcome.


Да возражений особо нет, так — уточнения.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[8]: Принцип подстановки Лисков (LSP из SOLID)
От: artelk  
Дата: 01.08.11 16:50
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>Здравствуйте, artelk, Вы писали:


A>>А пример приведи, плиз.


Z>
Z>if (x.GetType() == typeof(SomeConcreteType))
Z>{
Z>  //...
Z>}
Z>


У типов и у функций есть контракты. По сути это некие ограничения, накладываемые на них. Часть из них выражается явно и может быть проверена компилятором. Часть — не может быть выражена в рамках используемого языка программирования.
Для примера возьмем C#.
Если под контрактом типа подразумевать только набор сигнатур его методов и игнорировать эту неявную составляющую контракта, то любое наследование автоматом станет удовлетворять LSP и никакого смысла в этом принципе не будет.
В контракт функции входит, в частности, ее имя, типы ее параметров и тип возвращаемого значения. Проверка на соответствие передаваемых ей параметров ее контракту осуществляется статически на этапе компиляции.

class SomeBaseType
{...}

class SomeConcreteType: SomeBaseType
{...}

class SomeConcreteType2: SomeBaseType
{...}

void SomeFunc(SomeBaseType x)
{
  //...
  ((SomeConcreteType)x).SomeMethod();
  //...
}

Вопрос: что является контрактом этой функции и правильно ли он выражен синтаксически?
При передаче ей SomeConcreteType2 будет выброшен InvalidCastException.
  1. Если считать это ожидаемым поведением функции SomeFunc, то нарушения LSP нет.
  2. Если считать это ошибочным поведением, то контракт функции указан некорректно — ее параметр должен иметь тип SomeConcreteType. Просто проверка компилятором была подавлена.

class SomeConcreteType3: SomeBaseType
{...}

void SomeFunc(SomeBaseType x)
{
  //...
  if(x is SomeConcreteType)
  {
    ((SomeConcreteType)x).SomeMethod();
  }
  else
  {
    ((SomeConcreteType2)x).SomeMethodOfType2();
  }
  //...
}

Тут контрактом на параметр будет, что он не null и имеет тип SomeConcreteType или SomeConcreteType2. Такое условие невыразимо на C#, т.е. это неявный контракт.

Еще пример с неявным контрактом:
interface ISort<T>
  where T: IComparable<T>
{
   T[] Sort(IEnumerable<T> items);
}

class BadSort: ISort<int>
{
  int[] Sort(IEnumerable<int> items) { return items.ToArray(); }
}

Тут BadSort противоречит неявному контракту — он не делает сортировку.

В каких-то языках можно больше выразить явно, в каких-то меньше. В языках с зависимыми типами, например, можно даже требование сортировки выразить явно и BadSort вообще не скомпилируется...
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: Wolverrum Ниоткуда  
Дата: 01.08.11 17:38
Оценка:
Здравствуйте, igor-booch, Вы писали:

W>>Вы, когда наследуетесь от предка, специализируете поведение будущего объекта или, наоборот, обобщаете?


IB>специализирую

Вот Вам и ответ!
Re: Принцип подстановки Лисков (LSP из SOLID)
От: igor-booch Россия  
Дата: 02.11.11 14:39
Оценка: :)
http://blog.byndyu.ru/2009/10/blog-post_29.html
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Re[2]: Принцип подстановки Лисков (LSP из SOLID)
От: VladD2 Российская Империя www.nemerle.org
Дата: 02.11.11 21:28
Оценка: +1
Здравствуйте, LaptevVV, Вы писали:

LVV>Кстати о "правильности" наследования:

LVV>

LVV> спецификация. Дочерний класс реализует поведение, описанное в родительском классе. Ясно, что в С++ эта форма реализуется простым открытым наследованием от абстрактного класса;
LVV> конструирование. Класс-наследник использует методы базового класса, но не является его подтипом (принцип подстановки не выполняется). В С++ такую форму можно реализовать простым закрытым наследованием;
LVV> расширение. В класс-потомок добавляют новые методы, расширяя поведение родительского класса; принцип подстановки в такой форме выполняется;
LVV> обобщение. Дочерний класс обобщает поведение базового класса. Обычно такое наследование используется в тех случаях, когда мы не можем изменить поведение базового класса (например, базовый класс является библиотечным классом);
LVV> ограничение. Класс-наследник ограничивает поведение родительского класса. Очевидно, что в С++ такой вид наследования реализуется простым закрытым наследованием (пример — TUniversalDeque -> TStack );
LVV> варьирование. Базовый класс и класс-потомок являются вариациями на одну тему, однако связь «класс-подкласс» произвольна, например, «квадрат-прямоугольник» или «прямоугольник-квадрат». Эта форма фактически не отличается от «конструирования», так как класс-наследник, очевидно, «использует методы базового класса, но не является его подтипом»;
LVV> комбинирование. Дочерний класс наследует черты нескольких классов — это множественное наследование.


Что только народ не придумает, чтобы оправдать использование наследования не по назначению?!

А про возможность внутренние поля объявлять этот уважаемый автор не слышал?

ЗЫ

У меня иногда складывается впечатление, что ООП придумали для того, чтобы его использовали не по назначению.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: Lazin Россия http://evgeny-lazin.blogspot.com
Дата: 03.11.11 05:43
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>Предположим у нас есть класс SimpleLogger, у него виртуальный метод Log(string message), который просто выводит сообщения на консоль.

IB>Есть класс DbLogger, который наследуется от SimpleLogger. В DbLogger перекрыт метод Log(string message) и добавлена функциональность логирования сообщения в БД.
IB>Теперь меняем в программе SimpleLogger на DbLogger, опаньки, а баз данных то нет, комп от сети отключен, даже connection string нигде не указан. Логирование не работает.

LSP требует, что-бы контракты aka соглашения не ломались при смене реализации, в данном примере, если класс SimpleLogger гарантирует что сообщение будет записано всегда и никаких исключений брошено не будет(лог всегда доступен для записи), то класс DbLogger тоже должен это гарантировать, поэтому из принципа LSP следует, что класс DbLogger очень нетривиальная штука. Во первых, он должен как либо инициализироваться перед использованием и кидать свои исключения во время инициализации, если например не может подключиться к БД. Во вторых, он должен гарантировать непрерывную доступность для записи, даже если соединение с сервером БД по какой либо причине отвалилось. В этом случае он может например тупо выкидывать сообщения, либо временно откатиться на другой механизм логирования, он может буферизовать сообщения где нибудь, а потом записать их в БД, после того как соединение между ними будет восстановлено.
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: fmiracle  
Дата: 11.11.11 15:56
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>По-мойму это справедливо если классы наследники только добавляют новые методы. Если в классах наследниках происходит перекрытие методов базового класса, то такая подстановка сломает программу. Например базовый класс принтер, класс наследник цветной лазерный принтер. Метод печатать.


Принцип говорит, что при проектировании класса принтер, у которого будет еще много наследников, спецификация на его метод Print должна описывать этот метод достаточно обще, чтобы все наследники под него подпадали. Например, просто говорится, что "изображение будет распечатано на плоском листе бумаги". И программа, если использует именно класс Printer, а не его наследника, никак не должна делать каких-либо предположений, больших чем просто что после этой операции изображение попадет на бумагу. Не должно быть предположений, что оно будет цветным или чернобелым, высокой детализации или низкой, устойчивым к воде или нет. Если нужны какие-то более точные гарантии на получившийся результат, — используй другие базовые классы (или интерфейсы).
А если вдруг нужно что-то, что делает все же не в соответствии со спецификацией, ожидаемой в программе, то не надо использовать этот же базовый класс, а надо завести другой, даже если на первый взгляд поведение и сигнатура методов очень похожи. Опять же например — если для нашего принтера ожидается, что это будет распечатка на бумаге, то при необходимости создать принтер, печатающий на CD-болванках, или какой-нибудь 3D-принтер — для них надо создавать другие базовые классы.
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: k.o. Россия  
Дата: 12.11.11 07:29
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, LaptevVV, Вы писали:


LVV>>Кстати о "правильности" наследования:

LVV>>

LVV>> спецификация. Дочерний класс реализует поведение, описанное в родительском классе. Ясно, что в С++ эта форма реализуется простым открытым наследованием от абстрактного класса;
LVV>> конструирование. Класс-наследник использует методы базового класса, но не является его подтипом (принцип подстановки не выполняется). В С++ такую форму можно реализовать простым закрытым наследованием;
LVV>> расширение. В класс-потомок добавляют новые методы, расширяя поведение родительского класса; принцип подстановки в такой форме выполняется;
LVV>> обобщение. Дочерний класс обобщает поведение базового класса. Обычно такое наследование используется в тех случаях, когда мы не можем изменить поведение базового класса (например, базовый класс является библиотечным классом);
LVV>> ограничение. Класс-наследник ограничивает поведение родительского класса. Очевидно, что в С++ такой вид наследования реализуется простым закрытым наследованием (пример — TUniversalDeque -> TStack );
LVV>> варьирование. Базовый класс и класс-потомок являются вариациями на одну тему, однако связь «класс-подкласс» произвольна, например, «квадрат-прямоугольник» или «прямоугольник-квадрат». Эта форма фактически не отличается от «конструирования», так как класс-наследник, очевидно, «использует методы базового класса, но не является его подтипом»;
LVV>> комбинирование. Дочерний класс наследует черты нескольких классов — это множественное наследование.


VD>Что только народ не придумает, чтобы оправдать использование наследования не по назначению?!


VD>А про возможность внутренние поля объявлять этот уважаемый автор не слышал?


А ты про empty base class optimization, не слышал? Или про ADL? В C++ наследование, всё-таки, даёт некоторые возможности, которые с помощью аггрегации не получишь. Между прочим, кто сказал, что единственное назначение наследования это выражение отношения тип — подтип согласно LSP?

B. Liskov "Data Abstraction and Hierarchy"

We are using the words "subtype" and "supertype" here to emphasize that now we are talking about
a semantic distinction. By contrast, "subclass" and "superclass" are simply linguistic concepts in
programming languages that allow programs to be built in a particular way. They can be used to
implement subtypes, but also, as mentioned above, in other ways.

Re[4]: Принцип подстановки Лисков (LSP из SOLID)
От: VladD2 Российская Империя www.nemerle.org
Дата: 12.11.11 10:37
Оценка:
Здравствуйте, k.o., Вы писали:

KO>А ты про empty base class optimization, не слышал? Или про ADL?


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

Второе ADL (Argument-dependent name lookup) вообще никакого отношения к делу не имеет, так как относится к алгоритму разрешения перегрузки операторов и функций.

Короче, это смешно. Нахватался умных базвордов решил этим похвастаться?
Здорово, но не в тему.

KO>В C++ наследование, всё-таки, даёт некоторые возможности, которые с помощью аггрегации не получишь. Между прочим, кто сказал, что единственное назначение наследования это выражение отношения тип — подтип согласно LSP?


В C++, как и в любом другом ООЯ можно использовать ООП по делу — для реализации иерархий классов предметной области и для реализации Абстрактных Типов Данных. А можно не по делу.

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

KO>B. Liskov "Data Abstraction and Hierarchy"

KO>

KO>We are using the words "subtype" and "supertype" here to emphasize that now we are talking about
KO>a semantic distinction. By contrast, "subclass" and "superclass" are simply linguistic concepts in
KO>programming languages that allow programs to be built in a particular way. They can be used to
KO>implement subtypes, but also, as mentioned above, in other ways.


И что же ты тут такого вычитал?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: Принцип подстановки Лисков (LSP из SOLID)
От: k.o. Россия  
Дата: 12.11.11 11:54
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, k.o., Вы писали:


KO>>А ты про empty base class optimization, не слышал? Или про ADL?


VD>Тип решил поразить широтой своих познаний?


Нет, просто хотел напомнить, что в некоторых языках, всё, к сожалению, не так просто.

VD>Если я правильно понял первый базворд относится к борьбе за размеры экземпляров типов основанную на опитимизациях которые делают отдельные компиляторы — говоря проще занятие ерундой.


Имхо, для того чтобы называть программирование на C++ занятием ерундой есть другой форум. Для тех же кому этим приходится заниматься, размер занимаемой памяти иногда имеет очень важное значение.

VD>Второе ADL (Argument-dependent name lookup) вообще никакого отношения к делу не имеет, так как относится к алгоритму разрешения перегрузки операторов и функций.


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

KO>>В C++ наследование, всё-таки, даёт некоторые возможности, которые с помощью аггрегации не получишь. Между прочим, кто сказал, что единственное назначение наследования это выражение отношения тип — подтип согласно LSP?


VD>В C++, как и в любом другом ООЯ можно использовать ООП по делу — для реализации иерархий классов предметной области и для реализации Абстрактных Типов Данных. А можно не по делу.


ООП это одно, а конкретные языковые конструкции несколько другое. Можно использовать их для ООП, а можно и для других целей.

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


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

KO>>B. Liskov "Data Abstraction and Hierarchy"

KO>>

KO>>We are using the words "subtype" and "supertype" here to emphasize that now we are talking about
KO>>a semantic distinction. By contrast, "subclass" and "superclass" are simply linguistic concepts in
KO>>programming languages that allow programs to be built in a particular way. They can be used to
KO>>implement subtypes, but also, as mentioned above, in other ways.


VD>И что же ты тут такого вычитал?


Что назначение такой языковой конструкции как наследование не сводится к выражению отношения тип — подтип. А что ещё тут можно вычитать?
Re[2]: Принцип подстановки Лисков (LSP из SOLID)
От: ankf  
Дата: 12.11.11 15:36
Оценка:
Здравствуйте, LaptevVV, Вы писали:

LVV>Здравствуйте, igor-booch, Вы писали:


IB>>http://en.wikipedia.org/wiki/Liskov_substitution_principle


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

LVV>Это динамически происходит. "На место объекта базового класса МОЖНО подставить объект производного класса". Обратно — нельзя.
LVV>Будильник — это часы, но не всякие часы — будильник.

Не согласен, не всякий будильник — часы. Будильником может являться любой раздражитель.
Например на практике используются такие будильники по утрам как солнечный свет в окне, петух, уличный шум, голодная жена/дите/собака/кот.
Я программист, я Иван Помидоров, хватить трепатся — наш козырь error.
Re[3]: Принцип подстановки Лисков (LSP из SOLID)
От: LaptevVV Россия  
Дата: 12.11.11 16:32
Оценка:
Здравствуйте, ankf, Вы писали:

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

LVV>>Это динамически происходит. "На место объекта базового класса МОЖНО подставить объект производного класса". Обратно — нельзя.
LVV>>Будильник — это часы, но не всякие часы — будильник.

A>Не согласен, не всякий будильник — часы. Будильником может являться любой раздражитель.

A>Например на практике используются такие будильники по утрам как солнечный свет в окне, петух, уличный шум, голодная жена/дите/собака/кот.
Тогда так: всякий квадрат — четырехугольник, но не всякий четырехугольник — квадрат...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[4]: Принцип подстановки Лисков (LSP из SOLID)
От: ankf  
Дата: 12.11.11 16:41
Оценка:
Здравствуйте, LaptevVV, Вы писали:

LVV>Здравствуйте, ankf, Вы писали:


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

LVV>>>Это динамически происходит. "На место объекта базового класса МОЖНО подставить объект производного класса". Обратно — нельзя.
LVV>>>Будильник — это часы, но не всякие часы — будильник.

A>>Не согласен, не всякий будильник — часы. Будильником может являться любой раздражитель.

A>>Например на практике используются такие будильники по утрам как солнечный свет в окне, петух, уличный шум, голодная жена/дите/собака/кот.
LVV>Тогда так: всякий квадрат — четырехугольник, но не всякий четырехугольник — квадрат...

Тоже не согласен, например в музыке есть понятие квадрат , к четырехугольникам не имеющий отношение.
Есть математическая операция , например квадрат числа, означающая возведение в степень.
Я программист, я Иван Помидоров, хватить трепатся — наш козырь error.
Re[5]: Принцип подстановки Лисков (LSP из SOLID)
От: LaptevVV Россия  
Дата: 12.11.11 17:50
Оценка:
Здравствуйте, ankf, Вы писали:

LVV>>>>Будильник — это часы, но не всякие часы — будильник.


A>>>Не согласен, не всякий будильник — часы. Будильником может являться любой раздражитель.

A>>>Например на практике используются такие будильники по утрам как солнечный свет в окне, петух, уличный шум, голодная жена/дите/собака/кот.
LVV>>Тогда так: всякий квадрат — четырехугольник, но не всякий четырехугольник — квадрат...

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

A>Есть математическая операция , например квадрат числа, означающая возведение в степень.
Если мы пишем некую прогу, то ограничиваемся одной предметной областью. И не лезем в другую...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.