Продолжаем начатую в прошлом посту серию «Твердые объектно-ориентированные принципы» — теперь мы отдельно и подробно остановимся на Liskov Substitution Principle (LSP).
Глубоко вдохните, выдохните, а теперь поехали головой орехи колоть.
~
Вот резюме определения этого принципа (более подробно изложено в прошлой статье):
В характерных терминах это означает, что предопределенные условия метода/класса не могут быть усилены, постусловия не могут быть ослаблены, все возбуждаемые исключения должны быть в равной степени заменяемыми, и сигнатуры методов должны быть полностью совместимы.
Основные тезисы:
Когда мы пытаемся применить это на практике, бывает сложно это сделать. Так, с чего же начать... Давайте начнем с одного из первых примеров, который многие из нас видели, когда начинали обучаться ООП. Геометрические фигуры!
Вот простой класс Rectangle (прямоугольник):
class Rectangle { protected $_height = ''; protected $_width = ''; public function __construct($height, $width) { $this->_height = $height; $this->_width = $width; } public function getArea() { return $this->_height*$this->_width; } }
Этот класс не делает ничего особенного, но выражает определение прямоугольника.
Теперь давайте расширим этот класс и получим класс Square (квадрат).
Т.к. мы знаем, что все квадраты это прямоугольники, но не все прямоугольники это квадраты, то логично приходим к такой иерархии классов:
class Square extends Rectangle { public function __construct($side) { parent::__construct($side, $side); } }
К сожалению, у нас разные сигнатуры методов, т.е. мы нарушили требование №4.
Опаньки, так мы же сейчас это исправим:
class Square extends Rectangle { public function __construct($height, $width) { parent::__construct($height, $width); } }
Но сейчас мы пришли к тому, что наш Квадрат может быть неквадратным, т.о. надо добавить валидацию сторон:
class Square extends Rectangle { public function __construct($height, $width) { if ($height != $width) { throw new Exception("That isn't a square!"); } parent::__construct($height, $width); } }
Но теперь мы добавили новое исключение и добавили новые предусловия на вход...
Приехали — это опять нарушение требований из списка выше: №1 и №3.
Т.о. принцип LSP трудно правильно реализовать даже с очень простой структурой классов.
Конечно, это немного надуманный пример, но это первый объектно-ориентированный пример в большинстве книг и статей. Представьте, что было бы, если бы я начал с других фигур, и включил бы, например круг или треугольник?
Конечно, я могу быть неправ. Поправьте меня, если я ошибаюсь (и можете даже пнуть меня посильнее, если дотянетесь). Я хотел бы это понять вместе с вами, честно.
~
Для тех же, кто слишком умен для этого блога и уже занес руку чтобы аргументированно двинуть в ухо автора по поводу вышеприведенной "проблемы" LSP, попытаюсь остановить это импульсивное движение магическим уточнением — диаграмма Венна:
Поясняю: этот принцип (LSP) в терминах теории множеств будет звучать так:
Свойство q, выполняющееся для объектов множества (типа) S, должно выполняться для объектов подмножества (подтипа) T.
Это и есть тот самый ключ ко всем сложностям Принципа подстановки Барбары Лисков. Впрочем, более подробно об этом (и многом другом) мы поговорим уже в следующий раз.
~
В догонку: вот прекрасный пример для закрепления сути всего сказанного.
2 комментария
> Если мы не можем применить на практике один из основных принципов
> «хорошего» объектно-ориентированного дизайна здесь, менее чем в 20
> строках кода, то, как мы можем это сделать в реальном мире? Можно ли
> вообще ожидать этого?
Отлично ответил автор статьи на хабре:
> мы бы могли догадаться, что придуманная нами модель абстракции
> в новых реалиях — полное фуфло
И свои три копейки: любая модель, любая теория верна лишь в границах области применимости.
Вышли за границы - получите фейл, и вперед - к новой модели.
А принцип как раз и должен натолкнуть на мысль ДО того, как новый и дорогой контроллер температуры задымится
Если я правильно понял статью, то затея получилась бы, начни мы описывать сначала треугольники, с подтипами в виде прямоугольных и равносторонних, потом подтип четырехугольника, параллелограмма, трапеции, и таки прямоугольника..