Приветствую вас в 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()
), где , по возможности, выбирается нужный метод. В целом, смысл следующий: класс-наследник должен уметь работать со всеми методами родителя.