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.: Винодельческие провинции — это есть рулез!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.