Пытаясь проникнуть в суть одного из принципов SOLID, наткнулся на статью Роберта Мартина "Liskov substitution principle". Суть принципа без заумностей следующая: Базовый класс всегда можно заменить на производный от него без изменения поведения программы. https://drive.google.com/file/d/0BwhCYaYDn8EgNzAzZjA5ZmItNjU3NS00MzQ5LTkwYjMtMDJhNDU5ZTM0MTlh/view Там на квадрате и прямоугольнике приведен пример нарушения принципа. Я написал этот пример на php. PHP: <?php class Rect { protected $h; protected $w; public function SetWidth($w) {$this->w = $w;} public function SetHeight($h) {$this->h = $h;} public function GetHeight() {return $this->h;} public function GetWidth() {return $this->w;} }; class Sqr extends Rect { public function SetWidth($w) {$this->w = $this->h = $w;} public function SetHeight($h) {$this->h = $this->w = $h;} }; function test_rect(Rect $rect){ $w = 5; $h = 2; $rect->SetWidth($w); $rect->SetHeight($h); return $rect->GetWidth() * $rect->GetHeight() == $w * $h; } $rect = new Rect; $rect->SetWidth(5); $rect->SetHeight(2); $sqr = new Sqr; $sqr->SetWidth(5); echo 'var_dump $rect $sqr' . PHP_EOL; var_dump([$rect, $sqr]); echo 'test_rect $rect passed' . PHP_EOL; $rect = new Rect(); var_dump(test_rect($rect)); echo 'test_rect $sqr passed' . PHP_EOL; $rect = new Sqr(); var_dump(test_rect($rect)); В общем все с нарушением принципа мне было понятно. Проблема была в другом. Я никак не мог совместить в голове две в общем простые вещи. Первую в меня вдолбили еще в школе. "Квадрат - это разновидность прямоугольника и обладает всеми его свойствами." Класс квадрата не может наследовать класс прямоугольника, не нарушая LSP. Что же здесь не так? Как известно, правильный вопрос - половина ответа. Я сформулировал следующие вопросы: 1. Верно ли утверждение, что переопределение свойства базового класса в производном классе приводит к нарушению LSP? 2. Если переопределение свойств базового класса всегда приводит к нарушению LSP, зачем существует возможность переопределения свойств базового класса?
1. это возможно, но не факт. так же как и обратное тоже не гарантирует соблюдение принципа. вообще говоря, планируя классы надо думать прежде всего о поведении, а не о свойствах объекта. к сожалению нет четкой методологии как гарантировать что нужное нам поведение сохранится в наследниках. наверное потому, что нельзя предугадать какое именно поведение будет важно для клиента (т.е. контекста использования) 2. см выше главное: наша имплементация классов это не то же что абстракции предметной области. будут ли классы хрупкими, это зависит в-основном, от того какие требования и ограничения мы применяем. чем у́же контекст применения, тем меньше вероятность всё сломать. на хабре есть две статьи про LSP, упоминающие эту ситуацию с прямоугольником/квадратом.
Кто сказал, что квадрат не может наследоваться от прямоугольника, не переопределяя методов прямоугольника? Мы вполне можем внести в квадрат дополнительную (избыточную) функциональность, сохранив функциональность прямоугольника. И тогда принцип подстановки будет сохранён. Другое дело, что для нас важнее: сохранить принцип Барбары Лисков и пользоваться неудобным квадратом, или нарушить принцип, но сделать квадрат, удобный для дальнейшего использования? Золотое правило механики: выигрываем в силе - проигрываем в расстоянии. Золотое правило программирования: выигрываем в функциональности - проигрываем у Барбары Лисков. На мой взгляд (если не прав, более опытные коллеги поправят меня, надеюсь) все эти принципы программирования - это идеал. Идеал, как известно, недостижим, но стремиться к его достижению стоит. Я думаю, что для удобства программистов, для возможности написания более гибких программ. Но пользоваться этой возможностью или нет, а если пользоваться, то в какой степени и где пределы использования, каждый решает сам в зависимости от своей задачи. Точно также существует в РНР оператор goto. Но пользоваться ли им?
Эти принципы не просто идеал. Они помогают писать надёжный код, снижать стоимость его сопровождения в будущем. В упоминаемых статьях есть примеры тестов, которые будут проваливаться. Они не абстрактные, а вполне правдоподобные. Т.е. такой фрагмент мог бы внезапно™ приводить к ошибкам.
Нв стеке мне кинули красивый пример того, как LSP не нарушается, а override есть. Причем не сферический конь в вакууме, а такой кусок кода из жизни.
Я для себя на эти вопросы ответил так: 1. Да и нет, в зависимости от того, что считать использованием базового класса. 2. Для гибкости. Как времянка для освещения во время ремонта. Можно, если осторожно. Пример: Код (Text): Class Base { function foo(){return "baby";} } Class Child extends Base { function foo(){return "bitch";} } //использование кода $obj = new Base; $res = $obj->foo(); В этом примере, несмотря на абсурдность кода, видно, что переопределяй сколько хочешь, а Варя Лискова будет довольна. Я для себя по части LSP и override остановился на том, что буду исходить из того, что использование кода, это не только все фактические случаи использования, но и потенциальные. Т.е. если метод класса делает что-то, то именно это должен делать метод из производного класса.