Является ли вывод квадрата из прямоугольника нарушением принципа подстановки Лискова?
Я новичок в разработке и изучению принципов дизайна.
Он говорит, что вывод квадрата из прямоугольника является классическим примером нарушения принципа подстановки Лискова.
Если это так, то какой должна быть правильная конструкция?
8 ответов:
Я считаю, что рассуждение что-то вроде этого:
допустим, у вас есть метод, который принимает прямоугольник и регулирует его ширину:
public void SetWidth(Rectangle rect, int width) { rect.Width = width; }было бы вполне разумно, учитывая, что такое прямоугольник, предположить, что этот тест пройдет:
Rectangle rect = new Rectangle(50, 20); // width, height SetWidth(rect, 100); Assert.AreEqual(20, rect.Height);... поскольку изменение ширины прямоугольника не влияет на его высоту.
однако, допустим, вы получили новый квадратный класс из прямоугольника. По определению, квадрат имеет высоту и ширина всегда одинакова. Давайте попробуем этот тест еще раз:
Rectangle rect = new Square(20); // both width and height SetWidth(rect, 100); Assert.AreEqual(20, rect.Height);этот тест не пройдет, потому что установка ширины квадрата до 100 также изменит его высоту.
таким образом, принцип подстановки Лискова нарушается получением квадрата из прямоугольника.
правило "is-a" имеет смысл в "реальном мире" (квадрат определенно является своего рода прямоугольником), но не всегда в мире разработки программного обеспечения.
Edit
чтобы ответить на ваш Вопрос, Правильный дизайн, вероятно, должен заключаться в том, что как прямоугольник, так и Квадрат происходят от общего класса "полигон" или "форма", который не применяет никаких правил в отношении ширины или высоты.
ответ зависит от переменчивости. Если ваши классы прямоугольника и квадрата неизменяемы, то
SquareЭто действительно подтипRectangleи это совершенно нормально, чтобы получить первый из второго. В противном случае,RectangleиSquareможет подвергнутьIRectangleбез мутаторов, но выводить один из другого неправильно, так как ни один тип не является должным образом подтипом другого.
Я не согласен, что вывод квадрата из прямоугольника обязательно нарушает LSP.
в Примере Мэтта, если у вас есть код, который полагается на ширину и высоту независимо, то он фактически нарушает LSP.
Если, однако, вы можете заменить прямоугольник на квадрат везде в своем коде, не нарушая никаких предположений, то вы не нарушаете LSP.
Так что это действительно сводится к тому, что прямоугольник абстракция означает в код решение.
Я много боролся с этой проблемой в последнее время и думал, что добавлю свою шляпу в кольцо:
public class Rectangle { protected int height; protected int width; public Rectangle (int height, int width) { this.height = height; this.width = width; } public int computeArea () { return this.height * this.width; } public int getHeight () { return this.height; } public int getWidth () { return this.width; } } public class Square extends Rectangle { public Square (int sideLength) { super(sideLength, sideLength); } } public class ResizableRectangle extends Rectangle { public ResizableRectangle (int height, int width) { super(height, width); } public void setHeight (int height) { this.height = height; } public void setWidth (int width) { this.width = width; } }обратите внимание на последний класс
ResizableRectangle. Перемещая "размерность" в подкласс, мы получаем повторное использование кода, фактически улучшая нашу модель. Выглядит это так: квадрат не может быть свободно изменен оставаясь площади, а не площади прямоугольников. Не все прямоугольники могут быть изменены, поскольку площадью прямоугольник (и это не может быть свободно изменен при сохранении своей "идентичности"). (o_O) так что имеет смысл сделать базуRectangleкласс, который не может быть изменен, так как это дополнительное свойство некоторые прямоугольников.
предположим, что у нас есть прямоугольник класса с двумя (для простоты публичными) свойствами width,height. Мы можем изменить эти два свойства: R. width=1, r.height=2.
Теперь мы говорим, что квадрат-это прямоугольник. Но хотя утверждение "квадрат будет вести себя как прямоугольник" мы не можем установить .ширина=1 а .высота=2 на квадратном объекте (ваш класс, вероятно, регулирует ширину, если вы устанавливаете высоту и наоборот). Таким образом, есть по крайней мере один случай, когда объект типа Square не ведет себя как a Прямоугольник и поэтому вы не можете заменить их (полностью).
Я считаю, что методы OOD/OOP существуют, чтобы программное обеспечение могло представлять реальный мир. В реальном мире квадрат-это прямоугольник с равными сторонами. Квадрат является квадратом только потому, что у него равные стороны, а не потому, что он решил быть квадратом. Поэтому программа OO должна иметь дело с этим. Конечно, если процедура, создающая экземпляр объекта, хочет, чтобы он был квадратным, она может указать свойство length и свойство width как равные одной и той же сумме. Если программа использует объект должен знать позже, если он квадратный, ему нужно только спросить его. Объект может иметь логическое свойство только для чтения, называемое "квадрат". Когда вызывающая процедура вызывает его, объект может вернуться (длина = ширина). Теперь это может быть так, даже если объект rectangle является неизменяемым. Кроме того, если прямоугольник действительно является неизменяемым, значение свойства Square может быть установлено в конструкторе и сделано с ним. Почему тогда это проблема? ЛСП требует суб-объекты, которые должны быть неизменными в применить и Квадрат, являющийся подобъектом прямоугольника, часто используется в качестве примера его нарушения. Но это не кажется хорошим дизайном, потому что, когда процедура using вызывает объект как "objSquare", должна знать его внутреннюю деталь. Разве не было бы лучше, если бы ему было все равно, был ли прямоугольник квадратным или нет? И это было бы потому, что методы прямоугольника были бы правильными независимо. Есть ли лучший пример, когда LSP нарушается?
еще один вопрос: как это объект сделан неизменным? Есть ли "неизменяемое" свойство, которое можно установить при создании экземпляра?
Я нашел ответ и то, что я ожидал. Поскольку я разработчик VB .NET, это то, что меня интересует. Но понятия одинаковы в разных языках. В VB .NET вы создаете неизменяемые классы, делая свойства доступными только для чтения, и используете новый конструктор, чтобы позволить процедуре создания экземпляра указывать значения свойств при создании объекта. Вы также можете использовать константы для некоторые свойства и они всегда будут одинаковыми. От создания вперед объект является неизменным.
проблема в том, что то, что описывается, на самом деле не "тип", а кумулятивное эмерджентное свойство.
все, что у вас действительно есть, это четырехугольник, и что как "прямоугольность", так и "прямоугольность" - это просто возникающие артефакты, полученные из свойств углов и сторон.
вся концепция "квадрата" (или даже прямоугольника) - это просто абстрактное представление совокупности свойств объекта по отношению друг к другу и к рассматриваемому объекту, не тип объекта внутри и его самого.
здесь может помочь размышление о проблеме в контексте безтипного языка, потому что не тип определяет, является ли он "квадратом", а фактические свойства объекта, определяющие, является ли он "квадратом".
Я думаю, если вы хотите взять абстракцию еще дальше, вы даже не скажете, что у вас есть четырехугольник, но у вас есть многоугольник или даже просто форма.
его довольно просто :) более "базовый" класс (первый в цепочке деривации) должен быть самым общим.
например форма - > прямоугольник - > квадрат.
здесь квадрат является частным случаем прямоугольника (с ограниченными размерами), а прямоугольник-частным случаем формы.
сказал другой способ-использовать" это " тест. Оруженосец-это прямоугольник. Но прямоугольник не всегда квадрат.
Comments