За последние 24 часа нас посетили 22608 программистов и 1209 роботов. Сейчас ищут 784 программиста ...

Принцип подстановки Барбары Лисковой

Тема в разделе "Прочие вопросы по PHP", создана пользователем johovich, 6 янв 2021.

  1. johovich

    johovich Активный пользователь

    С нами с:
    24 авг 2016
    Сообщения:
    146
    Симпатии:
    17
    Пытаясь проникнуть в суть одного из принципов SOLID, наткнулся на статью Роберта Мартина "Liskov substitution principle".
    Суть принципа без заумностей следующая:
    Базовый класс всегда можно заменить на производный от него без изменения поведения программы.


    https://drive.google.com/file/d/0BwhCYaYDn8EgNzAzZjA5ZmItNjU3NS00MzQ5LTkwYjMtMDJhNDU5ZTM0MTlh/view

    Там на квадрате и прямоугольнике приведен пример нарушения принципа. Я написал этот пример на php.
    PHP:
    1. <?php
    2.  
    3. class Rect
    4. {
    5.     protected $h;
    6.     protected $w;
    7.     public function SetWidth($w) {$this->w = $w;}
    8.     public function SetHeight($h) {$this->h = $h;}
    9.     public function GetHeight() {return $this->h;}
    10.     public function GetWidth() {return $this->w;}
    11. };
    12.  
    13. class Sqr extends Rect
    14. {
    15.     public function SetWidth($w) {$this->w = $this->h = $w;}
    16.     public function SetHeight($h) {$this->h = $this->w = $h;}
    17. };
    18.  
    19.  
    20. function test_rect(Rect $rect){  
    21.     $w = 5;
    22.     $h = 2;
    23.     $rect->SetWidth($w);
    24.     $rect->SetHeight($h);
    25.     return $rect->GetWidth() * $rect->GetHeight() == $w * $h;
    26. }
    27.  
    28.  
    29. $rect = new Rect;
    30. $rect->SetWidth(5);
    31. $rect->SetHeight(2);
    32.  
    33. $sqr = new Sqr;
    34. $sqr->SetWidth(5);
    35.  
    36. echo 'var_dump $rect $sqr' . PHP_EOL;
    37. var_dump([$rect, $sqr]);
    38.  
    39. echo 'test_rect $rect passed' . PHP_EOL;
    40. $rect = new Rect();
    41. var_dump(test_rect($rect));
    42.  
    43. echo 'test_rect $sqr passed' . PHP_EOL;
    44. $rect = new Sqr();
    45. var_dump(test_rect($rect));
    В общем все с нарушением принципа мне было понятно. Проблема была в другом. Я никак не мог совместить в голове две в общем простые вещи. Первую в меня вдолбили еще в школе.
    "Квадрат - это разновидность прямоугольника и обладает всеми его свойствами."
    Класс квадрата не может наследовать класс прямоугольника, не нарушая LSP.
    Что же здесь не так?

    Как известно, правильный вопрос - половина ответа. Я сформулировал следующие вопросы:

    1. Верно ли утверждение, что переопределение свойства базового класса в производном классе приводит к нарушению LSP?
    2. Если переопределение свойств базового класса всегда приводит к нарушению LSP, зачем существует возможность переопределения свойств базового класса?
     
  2. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.076
    Симпатии:
    1.237
    Адрес:
    там-сям
    1. это возможно, но не факт. так же как и обратное тоже не гарантирует соблюдение принципа.
    вообще говоря, планируя классы надо думать прежде всего о поведении, а не о свойствах объекта. к сожалению нет четкой методологии как гарантировать что нужное нам поведение сохранится в наследниках. наверное потому, что нельзя предугадать какое именно поведение будет важно для клиента (т.е. контекста использования)
    2. см выше

    главное: наша имплементация классов это не то же что абстракции предметной области. будут ли классы хрупкими, это зависит в-основном, от того какие требования и ограничения мы применяем. чем у́же контекст применения, тем меньше вероятность всё сломать.

    на хабре есть две статьи про LSP, упоминающие эту ситуацию с прямоугольником/квадратом.
     
  3. alexphp

    alexphp Активный пользователь

    С нами с:
    5 дек 2019
    Сообщения:
    98
    Симпатии:
    12
    Кто сказал, что квадрат не может наследоваться от прямоугольника, не переопределяя методов прямоугольника? Мы вполне можем внести в квадрат дополнительную (избыточную) функциональность, сохранив функциональность прямоугольника. И тогда принцип подстановки будет сохранён. Другое дело, что для нас важнее: сохранить принцип Барбары Лисков и пользоваться неудобным квадратом, или нарушить принцип, но сделать квадрат, удобный для дальнейшего использования?
    Золотое правило механики: выигрываем в силе - проигрываем в расстоянии.
    Золотое правило программирования: выигрываем в функциональности - проигрываем у Барбары Лисков. :)

    На мой взгляд (если не прав, более опытные коллеги поправят меня, надеюсь) все эти принципы программирования - это идеал. Идеал, как известно, недостижим, но стремиться к его достижению стоит.

    Я думаю, что для удобства программистов, для возможности написания более гибких программ. Но пользоваться этой возможностью или нет, а если пользоваться, то в какой степени и где пределы использования, каждый решает сам в зависимости от своей задачи.
    Точно также существует в РНР оператор goto. Но пользоваться ли им? :)
     
  4. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.076
    Симпатии:
    1.237
    Адрес:
    там-сям
    Эти принципы не просто идеал. Они помогают писать надёжный код, снижать стоимость его сопровождения в будущем.

    В упоминаемых статьях есть примеры тестов, которые будут проваливаться. Они не абстрактные, а вполне правдоподобные. Т.е. такой фрагмент мог бы внезапно™ приводить к ошибкам.
     
    alexphp нравится это.
  5. johovich

    johovich Активный пользователь

    С нами с:
    24 авг 2016
    Сообщения:
    146
    Симпатии:
    17
    Нв стеке мне кинули красивый пример того, как LSP не нарушается, а override есть. Причем не сферический конь в вакууме, а такой кусок кода из жизни.
     
  6. johovich

    johovich Активный пользователь

    С нами с:
    24 авг 2016
    Сообщения:
    146
    Симпатии:
    17
    Я для себя на эти вопросы ответил так:
    1. Да и нет, в зависимости от того, что считать использованием базового класса.
    2. Для гибкости. Как времянка для освещения во время ремонта. Можно, если осторожно.

    Пример:
    Код (Text):
    1. Class Base
    2. {
    3. function foo(){return "baby";}
    4. }
    5.  
    6. Class Child extends Base
    7. {
    8. function foo(){return "bitch";}
    9. }
    10.  
    11. //использование кода
    12. $obj = new Base;
    13. $res = $obj->foo();
    В этом примере, несмотря на абсурдность кода, видно, что переопределяй сколько хочешь, а Варя Лискова будет довольна.


    Я для себя по части LSP и override остановился на том, что буду исходить из того, что использование кода, это не только все фактические случаи использования, но и потенциальные. Т.е. если метод класса делает что-то, то именно это должен делать метод из производного класса.
     
    #6 johovich, 6 янв 2021
    Последнее редактирование: 6 янв 2021