Здравствуйте, 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.: Винодельческие провинции — это есть рулез!