Означает ли этот код квадрат /прямоугольник Лискова?

Я просто хотел проверить, что я правильно понял LSP и могу его решить. Я беру классическую проблему прямоугольника /квадрата и пытаюсь решить следующее:

 class Rectangle{
    public $width;
    public $height;

    function setWidth($width){
        $this->width = $width;
    }

    function setHeight($height){
        $this->height = $height;
    }
}

class Square extends Rectangle{

    function setWidth($width){
        $this->width = $width;
        $this->height = $width;
    }

    function setHeight($height){
        $this->height = $height;
        $this->width = $height;
    }
}

Если у вас есть код вроде:

 function changeSize(Rectangle $rect){
  $rect->setWidth(10);
  $rect->setHeight(30);
  $this->assertEquals(10,$rect->width);
  $this->assertEquals(30,$rect->height);
}

Тогда, очевидно, прямоугольники и квадраты не являются взаимозаменяемыми, поскольку квадрат вводит ограничение для родительского класса. Поэтому квадрат не должен наследоваться от прямоугольников.

Но, конечно, мы можем согласиться, что квадрат и прямоугольник являются четырьмя односторонними фигурами? Это мое предложенное решение, основанное на этой предпосылке:

 abstract class AFourSidedShape{
    public $width;
    public $height;

    abstract public function __construct($width,$height);

    public function scaleUp($percentage){
        $this->height = $this->height + (($this->height / 100) * $percentage);
        $this->width = $this->width + (($this->width / 100) * $percentage);
    }

    public function scaleDown($percentage){
        $this->height = $this->height - (($this->height / 100) * $percentage);
        $this->width = $this->width - (($this->width / 100) * $percentage);
    }
}

class Rectangle extends AFourSidedShape{
    function __construct($width, $height){
        $this->width = $width;
        $this->height = $height;
    }
}

class Square extends AFourSidedShape{
    function __construct($width, $height){
        if($width != $height){
            throw new InvalidArgumentException('Sides must be equal');
        }else{
            $this->width = $width;
            $this->height = $height;
        }
    }
}

Наш код клиента должен быть изменен на:

 function changeSize(AFourSidedShape $shape){
  $origWidth = $shape->width;
  $origHeight = $shape->height;
  $shape->scaleUp(10);
  $this->assertEquals($origWidth + (($origWidth/100) * 10),$shape->width);
  $this->assertEquals($origHeight + (($origHeight/100) * 10),$shape->height);
}

Моя теория такова: прямоугольники и квадраты на самом деле представляют собой четырехсторонние формы, поэтому не должно быть проблемы с наследованием от абстрактного класса foursidedshape. Хотя квадрат все еще добавляет дополнительные ограничения в конструкторе (т. Е. Бросает ошибку, если стороны не равны), это не должно быть проблемой, поскольку мы не реализовали конструктор в абстрактном родительском классе и, следовательно, код клиента не следует делать предположения о том, что вы можете /не можете передать ему в любом случае.

Мой вопрос: я понял LSP, и этот новый дизайн решает проблему LSP для квадрата /прямоугольника?

При использовании интерфейсов, как предлагается:

 interface AFourSidedShape{
    public function setWidth($width);
    public function setHeight($height);
    public function getWidth();
    public function getHeight();
}

class Rectangle implements AFourSidedShape{
    private $width;
    private $height;

    public function __construct($width,$height){
        $this->width = $width;
        $this->height = $height;
    }

    public function setWidth($width){
        $this->width = $width;
    }

    public function setHeight($height){
        $this->height = $height;
    }

    //getwidth, getheight
}

class Square implements AFourSidedShape{
    private $width;
    private $height;

    public function __construct($sideLength){
        $this->width = $sideLength;
        $this->height = $sideLength;
    }

    public function setWidth($width){
        $this->width = $width;
        $this->height = $width;
    }

    public function setHeight($height){
        $this->height = $height;
        $this->width = $height;
    }

    //getwidth, getheight
}
6 голосов | спросил user1578653 25 32015vEurope/Moscow11bEurope/MoscowWed, 25 Nov 2015 16:10:04 +0300 2015, 16:10:04

1 ответ


10

LSP - это контракт класса, и унаследованные классы все равно должны выполнять тот же контракт, что и их базовый класс. Интерфейс только в коде обычно определяет только parts этого контракта, в основном синтаксическую часть, а семантика может быть частично задана описательными именами методов или параметров. Другие части контракта часто просто определяются в комментариях, добавляя «утверждения утверждения», используя конкретные языковые функции или они могут быть закодированы в модульные тесты.

Таким образом, если вы действительно решили нарушение LSP, это зависит от полного семантического договора вашего интерфейса. Если контракт выглядит так (это ИМХО, более очевидное поведение):

// contract: a four sided shape is an object with two individual, independent
// properties "width" and "height"
interface AFourSidedShape{
    public function setWidth($width);
    public function setHeight($height);
    public function getWidth();
    public function getHeight();
}

, то класс Square не выполняет тот же контракт, что и его базовый класс, поэтому он по-прежнему нарушает LSP. Можно было бы написать более формально с точки зрения «условий сообщения», проверяя, что каждый раз, когда вы вызываете setHeight, значение getWidth не изменяется, и наоборот.

Если, однако, ваш семантический контракт выглядит следующим образом:

 // contract: a four sided shape is an object with two 
 // properties "width" and "height" which must not 
 // be assumed to be independent; maybe changing one can change the other
 interface AFourSidedShape{
     // ...
 }

, то нет никакого нарушения LSP. Из вашего вопроса и того, как вы описываете ограничения конструктора, я предполагаю, что это контракт, который вы имеете в виду. Однако последний может нарушить принцип наименьшего удивления , «сеттер», который для некоторых объектов изменяет другое значение, а для других - нет, может быть ошеломляющим среднего пользователя вашего класса. Лучше избегать ставить такие сеттеры в общий интерфейс, LSP подчиняется или нет.

Боковое примечание : конечно, исходную проблему Square /Rectangle можно понять как «существует ли реализация, в которой квадрат получается из Прямоугольника или наоборот напрямую, что не нарушить LSP ». Если вы читаете это так, то @ScantRoger верен, и ваш дизайн решает другую проблему. Тем не менее, я прочитал проблему совсем по-другому, поскольку есть способ включить наследование для обработки прямоугольников и квадратов общим образом, не нарушая LSP , и для этой проблемы ответ «да, ваше предложение будет решить эту проблему ".

ответил Doc Brown 25 32015vEurope/Moscow11bEurope/MoscowWed, 25 Nov 2015 18:43:24 +0300 2015, 18:43:24

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132