Приветствую вас в 5-ом эпизоде сериала “ООП здорового человека”. Здесь речь пойдет о 3-ем принципе SOLID – принцип подстановки Лисков. С названия просто ничего не понятно… поскольку Лисков – это фамилия ученого-информатика по имени Барбара. В общем, этот принцип(как и многие) придуман не дядей Бобом, а всего-лишь пере озвучен и переосмыслен – за что, в конкретно этом случаи, огромный респект ему…
Теория
Респект потому, что в оригинале формулировка имеет сложное математическое определение. Для сравнения, оригинал:
Пусть q(x) является свойством, верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.
Книга Мартина:
Функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа, не зная об этом.
Простыми словами(вывод, которому я пришёл со второй формулировки, если подумать – первая о том же): проектируй код так, чтоб при замене объекта-родителя объектом-наследником система не падала. Вообще, этот принцип мне видится как частный случай The Open Closed Principle(проясните мне ваше виденье в комментах! только без токсика..).
Пример
В качестве примера, иллюстрирующего этот принцип, принято говорить о прямоугольнике и квадрате(частный случай прямоугольника) или метод передвижения птиц. На практике же(вероятно, так делают все промышленные прогеры(или нет), этим стоит заниматься когда точно знаешь направление развития проекта – в общем, у меня еще нет своих примеров под этот принцип(будут – допишу).
Практика
Коль собственных примеров нет(та и, в принципе – он будет аналогичен этим) – опишу фигуры и птиц в виде классов. Начнем с прямоугольника и квадрата. В прямоугольнике есть высота h, ширина w и добавим метод по вычислению площади area()
<?php class Rectangle { public int $h; public int $w; public function setH(int $h): void { $this->h = $h; } public function setW(int $w): void { $this->w = $w; } public function getArea(): int { return $this->w * $this->h; } } $rectangle = new Rectangle(); $rectangle->setH(8); $rectangle->setW(5); echo $rectangle->getArea(); /* Вывод: 40
и, поскольку квадрат это частный случай прямоугольника, кажется логичным квадрат наследовать от прямоугольника – но: задав высоту – как быть с шириной и наоборот?
Вот мой вариант решения этой задачи(уверен, не лучший, ты то точно знаешь лучший)
<?php abstract class Figure { protected $params; public function setParams(array|int $params): void { $this->params = $params; } abstract public function getArea(): int; } class Rectangle extends Figure { public function getArea(): int { return (count($this->params) > 1) ? $this->params[0] * $this->params[1] : throw new Exception('Set all params'); } } class Square extends Figure { public function getArea(): int { return pow($this->params,2); } } $rectangle = new Rectangle(); $rectangle->setParams([5,8]); echo $rectangle->getArea().PHP_EOL; $square = new Square(); $square->setParams(6); echo $square->getArea(); /* Вывод: 40 36
Он заключается в том, чтоб прямоугольник и квадрат не были в единой ветке наследования а произрастали от общего предка – абстрактной фигуры.
С птицами аналогична: одни летают, вторые плавают, третьи – ножками перемещаются. При работе с такими объектами нужно дергать за какой-то абстрактный метод(к примеру, move()
), где , по возможности, выбирается нужный метод. В целом, смысл следующий: класс-наследник должен уметь работать со всеми методами родителя.