Насколько я понял принцип гласит, что если в программе заменить базовые классы классами наследниками, то ничего не должно сломаться. По-мойму бред. Или я что-то неправильно понял.
Здравствуйте, igor-booch, Вы писали:
IB>http://en.wikipedia.org/wiki/Liskov_substitution_principle
IB>Насколько я понял принцип гласит, что если в программе заменить базовые классы классами наследниками, то ничего не должно сломаться. По-мойму бред. Или я что-то неправильно понял.
Это динамически происходит. "На место объекта базового класса МОЖНО подставить объект производного класса". Обратно — нельзя.
Будильник — это часы, но не всякие часы — будильник.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
LVV>Это динамически происходит.
Что значит динамически?
LVV>"На место объекта базового класса МОЖНО подставить объект производного класса".
По-мойму это справедливо если классы наследники только добавляют новые методы. Если в классах наследниках происходит перекрытие методов базового класса, то такая подстановка сломает программу. Например базовый класс принтер, класс наследник цветной лазерный принтер. Метод печатать.
LVV>Обратно — нельзя.
с этим я абсолютно согласен
LVV>Будильник — это часы, но не всякие часы — будильник.
с этим я тоже абсолютно согласен
Здравствуйте, igor-booch, Вы писали:
LVV>>"На место объекта базового класса МОЖНО подставить объект производного класса". IB>По-мойму это справедливо если классы наследники только добавляют новые методы. Если в классах наследниках происходит перекрытие методов базового класса, то такая подстановка сломает программу. Например базовый класс принтер, класс наследник цветной лазерный принтер. Метод печатать.
Для того принцип и существует, чтобы замена принтера на цветной не ломала программу. Почему она должна сломаться мне совершенно непонятно.
LVV>>Это динамически происходит. IB>Что значит динамически?
То есть во время работ ы программы
LVV>>"На место объекта базового класса МОЖНО подставить объект производного класса". IB>По-мойму это справедливо если классы наследники только добавляют новые методы. Если в классах наследниках происходит перекрытие методов базового класса, то такая подстановка сломает программу. Например базовый класс принтер, класс наследник цветной лазерный принтер. Метод печатать.
Дык это же ТОЛЬКО принцип.А уж как его использовать — дело программиста. Но обратите внимание, ВСЕ ОО-языки этот принцип поддерживают.
В С++ этот принцип работает для открытого наследования.
Собственно, виртуальность — это оно и есть...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, igor-booch, Вы писали:
IB>http://en.wikipedia.org/wiki/Liskov_substitution_principle
IB>Насколько я понял принцип гласит, что если в программе заменить базовые классы классами наследниками, то ничего не должно сломаться. По-мойму бред. Или я что-то неправильно понял.
Понял прочитанное ты правильно, да. А вот с выводами почему-то всё сложно.
С какой стати твоя программа должна ломаться из за замены в логгере,например,сущности отвечающей за вывод в stdout на сущность отвечающую за вывод в файл?
Здравствуйте, igor-booch, Вы писали:
IB>http://en.wikipedia.org/wiki/Liskov_substitution_principle
IB>Насколько я понял принцип гласит, что если в программе заменить базовые классы классами наследниками, то ничего не должно сломаться. По-мойму бред. Или я что-то неправильно понял.
Правильно понял, но это справедливо только при условии, что наследование (inheritance) типов в данной программной системе действительно выражает отношение "тип-подтип" в том смысле, который подразумевает LSP. Обычно полагают, что так оно и есть, но в отдельных случаях это правило может нарушаться.
Говоря другими словами, в программе ничего не должно сломаться при замене базовых классов классами-наследниками, если и только если классы спроектированы с учётом LSP.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, igor-booch, Вы писали:
LVV>>"На место объекта базового класса МОЖНО подставить объект производного класса". IB>По-мойму это справедливо если классы наследники только добавляют новые методы. Если в классах наследниках происходит перекрытие методов базового класса, то такая подстановка сломает программу. Например базовый класс принтер, класс наследник цветной лазерный принтер. Метод печатать.
Угу-угу, это как раз и есть источник сложностей ОО-проектирования. Вот и спроектируй программу так, чтобы замена класса MatrixPrinter на ColorPrinter проходила незаметно для использующего кода.
Собственно, LSP не случайно сформулирован, как "свойство q(x)...". Суть в том, что набор соглашений, справедливых для базового типа должен соблюдаться и для типа-наследника (если они LSP-compliant). Говорим мы при этом об интерфейсных методах, или, скажем, о побочных эффектах в виде генерации какого-то события с заданной периодичностью — не суть важно.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, LaptevVV, Вы писали:
LVV>Дык это же ТОЛЬКО принцип.А уж как его использовать — дело программиста. Но обратите внимание, ВСЕ ОО-языки этот принцип поддерживают. LVV>В С++ этот принцип работает для открытого наследования. LVV>Собственно, виртуальность — это оно и есть...
Точнее — на C++ можно реализовать LSP с помощью открытого наследования и виртуальных методов.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Предположим у нас есть класс SimpleLogger, у него виртуальный метод Log(string message), который просто выводит сообщения на консоль.
Есть класс DbLogger, который наследуется от SimpleLogger. В DbLogger перекрыт метод Log(string message) и добавлена функциональность логирования сообщения в БД.
Теперь меняем в программе SimpleLogger на DbLogger, опаньки, а баз данных то нет, комп от сети отключен, даже connection string нигде не указан. Логирование не работает.
Здравствуйте, igor-booch, Вы писали:
IB>Предположим у нас есть класс SimpleLogger, у него виртуальный метод Log(string message), который просто выводит сообщения на консоль. IB>Есть класс DbLogger, который наследуется от SimpleLogger. В DbLogger перекрыт метод Log(string message) и добавлена функциональность логирования сообщения в БД. IB>Теперь меняем в программе SimpleLogger на DbLogger, опаньки, а баз данных то нет, комп от сети отключен, даже connection string нигде не указан. Логирование не работает.
Теперь только объясни себе какой смысл имеет логгирование в базу там где этой базы нет? Может если базы нет то объект который в неё захочет писать должен так и сказать мол базы насяйника нет, а не крушить всю систему из за этого? Ну а кто то вышестоящий увидев такую пьянку может попытаться заменить этот ваш DbLogger на что то более подходящее, что гораздо проще будет если иерархия построена на LSP. И это нормальное поведение, где вы тут нарушение LSP увидели я не понимаю.
LSP относится к дизайну системы, он как бы говорит нам — "Поразмысли над тем что бы спроектировать систему вот так и вероятно в последствии не будет так мучительно больно за бесцельно потраченные человеко-часы саппорта."
Здравствуйте, igor-booch, Вы писали:
IB>Предположим у нас есть класс SimpleLogger, у него виртуальный метод Log(string message), который просто выводит сообщения на консоль. IB>Есть класс DbLogger, который наследуется от SimpleLogger. В DbLogger перекрыт метод Log(string message) и добавлена функциональность логирования сообщения в БД.
А почему DbLogger должен быть наследником SimpleLogger?
Здравствуйте, 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.: Винодельческие провинции — это есть рулез!
Здравствуйте, igor-booch, Вы писали:
IB>http://en.wikipedia.org/wiki/Liskov_substitution_principle
IB>Насколько я понял принцип гласит, что если в программе заменить базовые классы классами наследниками, то ничего не должно сломаться. По-мойму бред. Или я что-то неправильно понял.
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.
Здравствуйте, igor-booch, Вы писали:
IB>Предположим у нас есть класс SimpleLogger, у него виртуальный метод Log(string message), который просто выводит сообщения на консоль. IB>Есть класс DbLogger, который наследуется от SimpleLogger. В DbLogger перекрыт метод Log(string message) и добавлена функциональность логирования сообщения в БД. IB>Теперь меняем в программе SimpleLogger на DbLogger, опаньки, а баз данных то нет, комп от сети отключен, даже connection string нигде не указан. Логирование не работает.
Кривой пример. Для консольного логера тоже могут быть проблемы , нет консоли , нет райнайм библиотеки, не хватает памяти для логирования и т.п.
Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>Здравствуйте, LaptevVV, Вы писали:
LVV>>Дык это же ТОЛЬКО принцип.А уж как его использовать — дело программиста. Но обратите внимание, ВСЕ ОО-языки этот принцип поддерживают. LVV>>В С++ этот принцип работает для открытого наследования. LVV>>Собственно, виртуальность — это оно и есть...
ГВ>Точнее — на C++ можно реализовать LSP с помощью открытого наследования и виртуальных методов.
On 24.07.2011 17:54, igor-booch wrote:
> Насколько я понял принцип гласит, что если в программе заменить базовые классы > классами наследниками, то ничего не должно сломаться. По-мойму бред. Или я > что-то неправильно понял.
Здравствуйте, igor-booch, Вы писали:
IB>Предположим у нас есть класс SimpleLogger, у него виртуальный метод Log(string message), который просто выводит сообщения на консоль. IB>Есть класс DbLogger, который наследуется от SimpleLogger. В DbLogger перекрыт метод Log(string message) и добавлена функциональность логирования сообщения в БД. IB>Теперь меняем в программе SimpleLogger на DbLogger, опаньки, а баз данных то нет, комп от сети отключен, даже connection string нигде не указан. Логирование не работает.
Это всего лишь значит, что у тебя LSP не соблюдается — ССЗБ
On 24.07.2011 18:08, igor-booch wrote:
> LVV>"На место объекта базового класса МОЖНО подставить объект производного класса". > По-мойму это справедливо если классы наследники только добавляют новые методы. > Если в классах наследниках происходит перекрытие методов базового класса, то > такая подстановка сломает программу. Например базовый класс *принтер*, класс > наследник*цветной лазерный принтер*. Метод *печатать*.
Это -- принцип "как оно должно быть", а не принцип "как оно есть на самом деле
в неработающих программах".
Открытое наследование в ООП (в большинстве языков программирования)
позволяет тебе использовать принцип подстановки. Т.е. подставить
вместо объекта базового класса объект-наследник. Наличие этого
принципа даёт тебе гарантию, что программа твоя соберётся
(скомпилируется, загрузится и т.п.), т.е. язык программирования
тебе ДАСТ ЭТО СДЕЛАТЬ. Но что это будет работать естественно
никто не гарантирует. Это ДОЛЖНО работать в хорошо спроектированной
и закодированной программе, иначе в ООП просто нет никакого смысла.
Кстати, есть разные языки относительно соблюдения этого
принципа. Есть "статические" языки, где проверка типа происходит
на этапе компиляции программы (сборки). Там этот принцип
соблюдается относительно всего класса (предка и наследника)
целиком. Есть "динамические" языки, где на этапе сборки
программы конкретный тип не проверяется, а проверка эта
делается только во время работы на этапе посылки объекту
какого-то класса определённого сообщения (вызова метода то есть).
В таких языках класс-"наследник" даже может быть формально
не унаследованным от своего класса-"предка", и там уже речь
идёт о "соблюдении протоколов".
Здравствуйте, igor-booch, Вы писали:
IB>http://en.wikipedia.org/wiki/Liskov_substitution_principle
IB>Насколько я понял принцип гласит, что если в программе заменить базовые классы классами наследниками, то ничего не должно сломаться. По-мойму бред. Или я что-то неправильно понял.
Это признак корректности спроектированной системы типов и отношений между ними. Если он не соблюдается — ССЗБ.
Он должен соблюдаться, иначе ты получаешь кривую программу.
Фактически, это даже не принцип в том смысле, что "так бы делать хорошо".
LSP является определением отношения тип-подтип.
Другое дело — когда у тебя есть какие-то конструкции в языке, которые позволяют номинально это отношение выразить, даже если формально оно нарушено.
Тогда ты можешь выразить отношение тип-подтип, просто использовав сооответствующую синтаксическую конструкцию для выражения наследования в твоем языке (class Derived: public Base), но это не сделает их настоящей парой тип-подтип, пока ты не приложишь специальные усилия, чтобы удовлетворить LSP.
Причем, обрати внимание, LSP сформулирован с терминах свойств типов, а это может быть все, что угодно — от сигнатуры метода до времени времени его работы.
Т.е. свойство "можно позвать метод с таким-то именем и с такими параметрами" само по себе обычно бессмысленно, потому что тебе нужно чтоб не просто позвать, тебе нужно конкретное поведение, но это единственное, что могут проверить компиляторы автоматически. А неопытные программисты думают, что если проверки компилятора прошли — они уже все реализовали наследование правильно.
Реальное свойство, например, может быть выражено так: "У метода есть некоторые предусловия, и если они не нарушены, то будут соблюдаться некоторые постусловия" — проверка этого лежит целиком на программисте, проектирующем и реализующем производный класс.
Естественно, никто не запретит тебе какой-то принцип нарушить, но вот только ты уже себе сам будешь ЗБ, когда твоя программа начнет падать, вести себя странно, или просто с большим трудом поддаваться поддержке и изменениям, провоцируя ошибки на каждом шагу.
Каждая пара классов, нарушающая LSP — это подводные грабли.
Это же относится и к остальным принципам SOLID.
К примеру — никто не запрещает тебе нарушить Single responsibility principle и написать универсальный всемогутер — только, я думаю, ты сам знаешь, каким геморроем оборачивается поддержка такого монстра.