Решает ли это нарушение квадратно-прямоугольной замены Лискова?

Я новичок в принципах проектирования SOLID. Одна вещь, с которой у меня возникли проблемы с пониманием, - это пример "квадрат-прямоугольник" нарушения принципа замены Лискова. Почему установщик высоты/ширины квадрата должен переопределять параметры прямоугольника? Разве это не именно то, что вызывает проблему, когда есть полиморфизм?

Удаление не решает проблему?

class Rectangle
{
    public /*virtual*/ double Height { get; set; }
    public /*virtual*/ double Width { get; set; }
    public double Area() { return Height * Width; }
}

class Square : Rectangle
{
    double _width; 
    double _height;
    public /*override*/ double Height
    {
        get
        {
            return _height;
        }
        set
        {
            _height = _width = value;
        }
    }
    public /*override*/ double Width
    {
        get
        {
            return _width;
        }
        set
        {
            _width = _height = value;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Rectangle r = new Square();
        r.Height = 5;
        r.Width = 6;

        Console.WriteLine(r.Area());
        Console.ReadLine();
    }
}

Выход 30, как и ожидалось.


person Ivan Prodanov    schedule 09.08.2013    source источник
comment
Проблема в том, что клиенты Rectangle не ожидают, что установка одного измерения повлияет на другое.   -  person Lee    schedule 09.08.2013
comment
Обычное решение состоит в том, чтобы получить как Square, так и Rectangle от общего предка, называемого Shape или Polygon и т. д.   -  person David Elliman    schedule 09.08.2013


Ответы (2)


Представьте, что пользователь реализует ограничивающую рамку в приложении с графическим интерфейсом, примерно так:

введите здесь описание изображения

Они хотят представить это синее поле классом Rectangle, чтобы, если пользователь щелкнет и перетащит вниз, его высота увеличится; если пользователь перетащит вправо, его ширина увеличится.

LSP утверждает, что клиент должен иметь возможность использовать производный класс (Square) везде, где бы вы использовали его суперкласс (Rectangle), не нарушая бизнес-логику Rectangle, т. е. пользователь должен иметь возможность подменять один класс другим и остальными. их код не должен ломаться.

Но следующие несовместимы друг с другом:

  • Предполагается, что пост-условие Rectangle заключается в том, что методы установки не вызывают побочных эффектов (т. е. setWidth не должны влиять на высоту)
  • Логике квадрата свойственно то, что его ширина всегда будет равна его высоте.

Если бы программист использовал Square вместо Rectangle, их предположение выше не сработало бы, как если бы пользователь перетаскивал вниз, прямоугольник увеличивался по горизонтали и вертикали одновременно.


Проблема с примером Square/Rectangle заключается в том, что мы слишком много предполагаем о Rectangle. Длина прямоугольника может отличаться от его высоты, но это свойство определенного типа прямоугольника (продолговатого прямоугольника).

Квадрат является прямоугольником, но квадрат не является продолговатым прямоугольником. Если мы хотим предположить, что наш класс Rectangle ведет себя как продолговатый (что его ширина и высота могут отличаться), то для нашего класса Square не имеет смысла расширяться от него.

person anotherdave    schedule 15.08.2013

LSP утверждает, что замена объекта подкласса не должна изменять поведение или корректность программы. Классы, которые вы указываете, изменяют правильность. В случае с прямоугольником клиент класса ожидает, что высота и ширина могут устанавливаться независимо друг от друга. Когда вы создаете подкласс с Square, это уже не так.

Клиент, устанавливающий ширину 5 и высоту 10, ссылаясь на объект, который является квадратом, но хранится в переменной Rectangle, получит разные результаты в соответствии с порядком установки свойств высоты и ширины. Они могут получить прямоугольник 5x5 или 10x10. Любой случай будет неожиданным.

Существует оригинальное сложное описание LSP, данное Барбарой, но дядя Боб упрощает его: «Функции, использующие указатели или ссылки на базовые классы, должны иметь возможность использовать объекты производных классов, не зная об этом». Это не работает с проблемой Квадрат/Прямоугольник.

Я написал об этом статью на http://www.blackwasp.co.uk/SquareRectangle.aspx< /а>.

person BlackWasp    schedule 15.08.2013
comment
Я бы сказал, что наличие независимо устанавливаемой ширины и высоты не является неотъемлемым свойством прямоугольника. Прямоугольник остается прямоугольником, даже если его ширина и высота неизменяемы. Следовательно, для любого заданного прямоугольника разумно не делать никаких предположений об ограничениях (или отсутствии ограничений) на его размеры. Я бы сказал, что прямоугольник, который гарантированно имеет независимо регулируемую ширину и высоту, на самом деле не является чистым прямоугольником, это прямоугольник ПЛЮС определенное дополнительное гарантированное поведение. - person Brad Thomas; 24.12.2016
comment
замена объекта подкласса не должна изменять поведение, на самом деле это не так. Принцип Лисков говорит, что если программный модуль использует базовый класс, то ссылка на базовый класс может быть заменена производным классом, не влияя на функциональность - person jpl; 24.11.2017