Здравствуйте, Alex Dav, Вы писали:
AD>Расскажите, плиз, только попроще и по русски AD>Спасибо.
А что там военного? Не совсем по русски здесь.
Выдержка:
FUNCTIONS THAT USE POINTERS OR REFERENCES TO BASE CLASSES MUST BE ABLE TO USE OBJECTS OF DERIVED CLASSES WITHOUT KNOWING IT
Above is a paraphrase of the Liskov Substitution Principle (LSP). Barbara Liskov first
it as follows nearly 8 years ago. What is wanted here is something like the following substitution property: If
for each object o of type S there is an object o of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o is substituted for o then S is a subtype of T.
И пример кода нарушающего этот принцип:
void DrawShape(const Shape& s)
{
if (typeid(s) == typeid(Square))
DrawSquare(static_cast<Square&>(s));
else if (typeid(s) == typeid(Circle))
DrawCircle(static_cast<Circle&>(s));
}
Здравствуйте, Alex Dav, Вы писали:
AD>Расскажите, плиз, только попроще и по русски AD>Спасибо.
Введение в программу новых порожденных типов не должно изменять логику работы функционала, зависящего от базовых типов. Чаще всего нарущения допусткаются в коде, использующем инфоррмацию о типах в runtime.
Шурыгин Сергей
"Не следует преумножать сущности сверх необходимости" (с) Оккам
Здравствуйте, Alex Dav, Вы писали:
AD>Расскажите, плиз, только попроще и по русски
В ООП примерно так: Объект производного типа является также и объектом базового типа; на место объекта базового типа всегда может быть подставлен объект производного типа... То же касается и указателей и ссылок...
Обратное — неверно! Всякий будильник (производный тип) является часами (базовый тип), но не всякие часы — будильник.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, VladGalkin, Вы писали:
VG>Здравствуйте, Alex Dav, Вы писали:
VG>
VG>FUNCTIONS THAT USE POINTERS OR REFERENCES TO BASE CLASSES MUST BE ABLE TO USE OBJECTS OF DERIVED CLASSES WITHOUT KNOWING IT
VG>Above is a paraphrase of the Liskov Substitution Principle (LSP). Barbara Liskov first
VG>it as follows nearly 8 years ago. What is wanted here is something like the following substitution property: If
VG>for each object o of type S there is an object o of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o is substituted for o then S is a subtype of T.
Кстати, всегда обсуждение подобных проблем у меня вызывало какое-то подспудное чувство несогласия.
Познакомившись с паттерн-матчингом чуство сильно укрепилось. Я даже стал замечать, что порой нарушение ОО-традиций дает отличный результат.
Забавно было бы осудить насколько этот принцип вообще состоятелен? Не вызван ли он скудностью выразительных средств языка?
Я согласен, что если думать в понятиях ООП, то все выглядит более менее нормально. Но ведь можно думать и по другому!
Возьмем тот же паттерн Посетитель. Вся его суть заключается в попытке ранушить этот принцип. Мы как бы пытаемся изменить направление нашего взгляда на проблему. Вывести метод за пределы класса. Причем в ООЯ это получается очень неуклюже. А на языке с паттерн-матчингом просто и элегантно:
DrawShape(shape : Shape) : void
{
| square is Square => DrawSquare(square);
| circle is Circle => DrawCircle(circle);
| _ => DoDefaultAction(shape);
}
Есть конечно проблема расширения функциональности, но она иногда оказывается меньшей проблемой нежели наследование.
А применение алгеброических типов дает вообще новый взгляд на проблему. Применяя закрытые алгеброические типы мы спокойно можем контролировать полноту обрабоки и вносить нужную функциональность во все места где она требуется.
В общем, эти прицыпы рассчитаны на ООП. А ООП не всегда лучший выбор. Так что по-моему не все так однозначно.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Sshur, Вы писали:
S>Введение в программу новых порожденных типов не должно изменять логику работы функционала, зависящего от базовых типов. Чаще всего нарущения допусткаются в коде, использующем инфоррмацию о типах в runtime.
На самом деле это догма. А по жизни могут быть разные приоритеты. Например, иногда просто нет возможности ввести виртуальные члены или не хочется создавать наследников. При этом внешнее решение может оказаться очень выгодным. Кое как проблему позволяет решить паттерн Посетитель, но увы у него море своих проблем.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, Sshur, Вы писали:
S>>Введение в программу новых порожденных типов не должно изменять логику работы функционала, зависящего от базовых типов. Чаще всего нарущения допусткаются в коде, использующем инфоррмацию о типах в runtime.
VD>На самом деле это догма. А по жизни могут быть разные приоритеты. Например, иногда просто нет возможности ввести виртуальные члены или не хочется создавать наследников. При этом внешнее решение может оказаться очень выгодным. Кое как проблему позволяет решить паттерн Посетитель, но увы у него море своих проблем.
Здравствуйте, dottedmag, Вы писали:
D>Здравствуйте, Alxndr, Вы писали:
A>>Вообще-то это иллюстрация нарушения Open-Closed Principle. Похоже, к LSP пример иммет мало отношения
D>Одно другому не мешает. LSP — это всего лишь OCP, применённый к классам.
Ещё как мешает... LSP, вообще-то, имеет давольно опосредованное отношение к классам, как таковым, и к ООП — в частности. А Вы, скорее всего путаете, subtyping и subclassing.
Здравствуйте, VladD2, Вы писали:
VD>Кстати, всегда обсуждение подобных проблем у меня вызывало какое-то подспудное чувство несогласия. VD>Познакомившись с паттерн-матчингом чуство сильно укрепилось. Я даже стал замечать, что порой нарушение ОО-традиций дает отличный VD>результат.
Чувство возникло и у меня (о pattern matching узнал угадайте сами при знакомстве с каким языком программирования ), однако я забил на него, поскольку надо четко осознавать что одним ООП и виртуальными методами все не исчерпывается, и профессионал должен хотя бы знать, хм, иные (я нарочно не написал "более мощные" ) концепции и средства (почему собственно сейчас и пытаюсь читать SICP )
VD>Забавно было бы осудить насколько этот принцип вообще состоятелен? Не вызван ли он скудностью выразительных средств языка? VD>Я согласен, что если думать в понятиях ООП, то все выглядит более менее нормально. Но ведь можно думать и по другому!
Все зависит от точки зрения и концепции. В более простом, перефразированном виде концепция LSP звучит как
Subtypes must be substitutable for their base types
(С) Robert Martin. А при pattern matching такое определение, фактически, выкидывается. Но никто
и не говорит использовать LSP при pattern matching.
VD>Возьмем тот же паттерн Посетитель. Вся его суть заключается в попытке ранушить этот принцип. Мы как бы пытаемся изменить направление VD>нашего взгляда на проблему. Вывести метод за пределы класса. Причем в ООЯ это получается очень неуклюже. А на языке с VD>паттерн-матчингом просто и элегантно
Тут уже приводили цитату про паттерны GoF и динамические языки В данном случае она также справедлива на 99,9%.
VD>В общем, эти прицыпы рассчитаны на ООП. А ООП не всегда лучший выбор. Так что по-моему не все так однозначно. VD>Тут уже пробегала цитата про паттерны GoF и динамические языки
Я, собственно, в контексте только ООП его и рассматривал, т.к. человек, видимо, хотел услышать ответ применительно к ООП , однако я рад таким развернутым замечаниям и дискуссии.
Здравствуйте, gbear, Вы писали:
G> Ещё как мешает... LSP, вообще-то, имеет давольно опосредованное отношение к классам, как таковым, и к ООП — в частности. А Вы, скорее всего путаете, subtyping и subclassing.
Я прекрасно знаю разницу между subtyping и subclassing. А вам бы неплохо узнать, что в ОО-языках типы выражаются классами.
Здравствуйте, VladD2, Вы писали:
VD>На самом деле это догма.
Это не догма, это суровая правда жизни... А когда в реальном коде сталкиваешься с нарушением принципа в вышеприведенной формулировке, то это вообще — попа, волосатая, в прыщах.
VD> А по жизни могут быть разные приоритеты.
Дело не в приоритетах, а в том, что код какого-нибудь левого модуля может порушить все приложение, при казалось бы совершенно корректном соблюдении контрактов. Что является архитектурным косяком и никакими приоритетами не оправдывается....
VD> Например, иногда просто нет возможности ввести виртуальные члены или не хочется создавать наследников. При этом внешнее решение может оказаться очень выгодным. Кое как проблему позволяет решить паттерн Посетитель, но увы у него море своих проблем.
Не зацикливайся на ООП, говоря проще — код, который пользуется некоторым функционалом по заранее оговоренному контракту, не должен зависеть от конкретной реализации этого контракта.
Здравствуйте, gbear, Вы писали:
G>LSP, ЕМНИП, является [первой?] попыткой (не очень удачной, ИМХО) сформулировать положения т.н. behavioural subtyping.
А именно поэтому говорить о нарушении этого принципа — примерно то же, что говорить о нарушении принципа наименьшего действия.
Здравствуйте, LaptevVV, Вы писали:
LVV>Обратное — неверно! Всякий будильник (производный тип) является часами (базовый тип), но не всякие часы — будильник.
Я бы сказал несколько иначе. Существуют две функции:
1) Измерение времени.
2) Выдача сообщения (например, звонка) по сигналу.
Эти функции могут быть реализованы как в одном устройстве, так и в разных. Это я к тому, что далеко не всякий будильник является или может являться часами.
PROCEDURE (this: MyObject) HandleMessage (VAR msg: Message);
BEGIN
WITH msg: KeyDownMsg DO ... | msg: MouseMoveMsg DO ... ELSE ... END
END HandleMessage;
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Странно, следующий код: СГ>TYPE Message = ABSTRACT RECORD END; СГ>... СГ>PROCEDURE (this: MyObject) HandleMessage (VAR msg: Message); СГ>BEGIN СГ> WITH msg: KeyDownMsg DO ... | msg: MouseMoveMsg DO ... ELSE ... END СГ>END HandleMessage;
"И эти люди запрещают мне ковырятся в носу" (C) Это ж какой, пардон, "синтаксический оверхед", для всех этих msg: KeyDownMsg DO, вместо одного "msg.DoSomething()"
СГ>вроде аналогичный, а ничего не нарушает
Почему не нарушает? Потому что Вы так утверждаете? Я не знаю Oberon-2 (насколько я понял поддержка ООП появилась в нем). Вобщем почитал я про Oberon-2, и насколько понял, ООП там в понимании Вирта. Вот цитата:
Oberon-2 introduced limited reflection, methods ("type bound procedures"), and single inheritance ("type extension") without interfaces or mixins. Method calls were resolved at run-time, and limited polymorphism was possible by either overloading methods or by explicit typecase ("with" statement).
Дискутировать на тему Oberon, синтаксического оверхеда и Вирта я не намерен, говорю сразу.
Здравствуйте, VladGalkin, Вы писали:
СГ>>TYPE Message = ABSTRACT RECORD END; СГ>>... СГ>>PROCEDURE (this: MyObject) HandleMessage (VAR msg: Message); СГ>>BEGIN СГ>> WITH msg: KeyDownMsg DO ... | msg: MouseMoveMsg DO ... ELSE ... END СГ>>END HandleMessage;
VG>вместо одного "msg.DoSomething()"
У объекта сообщения Message не может быть метода DoSomething!!! У него вообще ничего нету, это абстрактный тип-пустышка = ABSTRACT RECORD END.
Откуда сообщению знать как его будет обрабатывать какой-то конкретный получатель?
У сообщения не может быть метода его обработки — cообщение пришло, а уж как на него реагировать — это целиком в компетенции получателя.
Несколько примеров типов объектов сообщений:
TYPE
CursorMessage = ABSTRACT RECORD (RequestMessage)
x, y: INTEGER
END;
DropMsg = RECORD (TransferMessage)
view: Views.View;
isSingle: BOOLEAN;
w, h, rx, ry: INTEGER
END;
EditMsg = RECORD (RequestMessage)
op: INTEGER;
modifiers: SET;
char: CHAR;
view: Views.View;
w, h: INTEGER;
isSingle, clipboard: BOOLEAN
END;
MarkMsg = RECORD (Views.CtrlMessage)
show, focus: BOOLEAN
END;
Message = Views.CtrlMessage;
PageMsg = RECORD (Views.CtrlMessage)
op, pageX, pageY: INTEGER;
done, eox, eoy: BOOLEAN
END;
PollCursorMsg = RECORD (CursorMessage)
cursor: INTEGER;
modifiers: SET
END;
PollDropMsg = RECORD (TransferMessage)
mark, show: BOOLEAN;
type: Stores.TypeName;
isSingle: BOOLEAN;
w, h, rx, ry: INTEGER;
dest: Views.Frame
END;
PollFocusMsg = EXTENSIBLE RECORD (Views.CtrlMessage)
focus: Views.Frame
END;
PollOpsMsg = RECORD (Views.CtrlMessage)
type, pasteType: Stores.TypeName;
singleton: Views.View;
selectable: BOOLEAN;
valid: SET
END;
PollSectionMsg = RECORD (Views.CtrlMessage)
focus, vertical: BOOLEAN;
wholeSize, partSize, partPos: INTEGER;
valid, done: BOOLEAN
END;
ReplaceViewMsg = RECORD (RequestMessage)
old, new: Views.View
END;
RequestMessage = ABSTRACT RECORD (Views.CtrlMessage)
requestFocus: BOOLEAN
END;
ScrollMsg = RECORD (Views.CtrlMessage)
focus, vertical: BOOLEAN;
op, pos: INTEGER;
done: BOOLEAN
END;
SelectMsg = RECORD (Views.CtrlMessage)
set: BOOLEAN
END;
TickMsg = RECORD (Views.CtrlMessage)
tick: INTEGER
END;
TrackMsg = RECORD (CursorMessage)
modifiers: SET
END;
TransferMessage = ABSTRACT RECORD (CursorMessage)
source: Views.Frame;
sourceX, sourceY: INTEGER
END;
WheelMsg = RECORD (CursorMessage)
done: BOOLEAN;
op, nofLines: INTEGER
END;
Например, рассмотрим сообщение WheelMsg:
TYPE WheelMsg (CursorMessage)
Это сообщение посылается, когда колесо на мыши с колесом поворачивается.
done: BOOLEAN
Если отображение обрабатывает это сообщение, оно должно установить флаг done в TRUE.
op: INTEGER
Показывает, какой тип события колеса мыши произошел. Используются те же константы, что и для прокрутки, но допустимы только следующие из них: incPage, decPage, incLine and decLine.
nofLines: INTEGER nofLines >= 1
Если op или icnLine, или decLine, то nofLines показывает, сколько строк должно быть прокручено. Для incPage и decPage это значение не определено.
Кстати, это Component Pascal, а не Oberon-2, что видно по использованному ключевому слову ABSTRACT.