Development Tip

직사각형에서 정사각형을 도출하는 것은 Liskov의 대체 원리를 위반하는 것입니까?

yourdevel 2020. 12. 10. 21:21
반응형

직사각형에서 정사각형을 도출하는 것은 Liskov의 대체 원리를 위반하는 것입니까?


저는 디자인 원칙을 처음 배우고 디자인했습니다.

직사각형에서 정사각형을 도출하는 것이 Liskov의 대체 원칙을 위반 한 전형적인 예라고 말합니다.

그렇다면 올바른 디자인은 무엇입니까?


나는 추론이 다음과 같다고 생각합니다.

직사각형을 받아들이고 너비를 조정하는 메서드가 있다고 가정 해 보겠습니다.

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에서 새 Square 클래스를 파생했다고 가정 해 보겠습니다. 정의에 따라 정사각형은 높이와 너비가 항상 동일합니다. 해당 테스트를 다시 시도해 보겠습니다.

Rectangle rect = new Square(20); // both width and height

SetWidth(rect, 100);

Assert.AreEqual(20, rect.Height);

사각형의 너비를 100으로 설정하면 높이도 변경되므로이 테스트는 실패합니다.

따라서 Liskov의 대체 원리는 Rectangle에서 Square를 도출함으로써 위반됩니다.

"is-a"규칙은 "실제 세계"(정사각형은 확실히 일종의 직사각형 임)에서 의미가 있지만 소프트웨어 디자인 세계에서는 항상 그런 것은 아닙니다.

편집하다

질문에 답하려면 Rectangle과 Square가 모두 너비 나 높이에 관한 규칙을 적용하지 않는 일반적인 "Polygon"또는 "Shape"클래스에서 파생되는 것이 올바른 디자인이어야합니다.


대답은 가변성에 달려 있습니다. 직사각형 및 정사각형 클래스가 불변이라면 Square실제로는의 하위 유형이며 Rectangle두 번째에서 첫 번째를 파생하는 것이 완벽합니다. 그렇지 않으면, Rectangle그리고 Square둘 모두 노출 될 수 IRectangle없음 뮤 테이터와 함께,하지만 어느 유형이 제대로 다른 서브 타입이기 때문에 서로를 유도하는 것은 잘못된 것입니다.


사각형에서 정사각형을 도출하는 것이 반드시 LSP를 위반한다는 데 동의하지 않습니다.

Matt의 예에서 너비와 높이가 독립적 인 코드가 있다면 실제로 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. "크기 조정"을 하위 클래스로 이동함으로써 실제로 모델을 개선하면서 코드를 재사용 할 수 있습니다. 다음과 같이 생각하십시오. 정사각형은 정사각형을 유지하면서 자유롭게 크기를 조정할 수 없지만 정사각형이 아닌 직사각형은 가능합니다. 하지만 정사각형 은 직사각형 이기 때문에 모든 직사각형의 크기를 조정할 수있는 것은 아닙니다 (그리고 "ID"를 유지하면서 자유롭게 크기를 조정할 수 없습니다). (o_O) 따라서 크기를 조정할 수없는 기본 클래스 를 만드는 것이 합리적입니다. 이것은 일부 사각형 의 추가 속성이기 때문 입니다.Rectangle


Let's assume we have the class Rectangle with the two (for simplicity public) properties width,height. We can change those two properties: r.width=1, r.height=2.
Now we say a Square is_a Rectangle. But though the claim is "a square will behave like a rectangle" we can't set .width=1 and .height=2 on a square object (your class probably adjusts the width if you set the height and vice versa). So there's at least one case where an object of type Square doesn't behave like a Rectangle and therefore you cannot substitute them (completely).


I believe that OOD/OOP techniques exist to enable software to represent the real world. In the real world a square is a rectangle that has equal sides. The square is a square only because it has equal sides, not because it decided to be a square. Therefore, the OO program needs to deal with it. Of course, if the routine instantiating the object wants it to be square, it could specify the length property and the width property as equal to the same amount. If the program using the object needs to know later if it is square, it needs only to ask it. The object could have a read-only Boolean property called “Square”. When the calling routine invokes it, the object can return (Length = Width). Now this can be the case even if the rectangle object is immutable. In addition, if the rectangle is indeed immutable, the value of the Square property can be set in the constructor and be done with it. Why then is this an issue? The LSP requires sub-objects to be immutable to apply and square being a sub-object of a rectangle is often used as an example of its violation. But that doesn’t seem to be good design because when the using routine invokes the object as “objSquare”, must know its inner detail. Wouldn’t it be better if it didn’t care whether the rectangle was square or not? And that would be because the rectangle’s methods would be correct regardless. Is there a better example of when the LSP is violated?

One more question: how is an object made immutable? Is there an “Immutable” property that can be set at instantiation?

I found the answer and it is what I expected. Since I'm a VB .NET developer, that is what I'm interested in. But the concepts are the same across languages. In VB .NET you create immutable classes by making the properties read-only and you use the New constructor to allow the instantiating routine to specify property values when the object is created. You can also use constants for some of the properties and they will always be the same. From creation forward the object is immutable.


The problem is that what is being described is really not a "type" but an cumulative emergent property.

All you really have is a quadrilateral and that both "squareness" and "rectangleness" are just emergent artifacts derived from properties of the angles and sides.

The entire concept of "Square" (or even rectangle) is just an abstract representation of a collection of properties of the object in relation to each other and the object in question, not the type of object in and of it's self.

This is where thinking of the problem in the context of a typeless language can help, because it's not the type that determines if it's "a square" but the actual properties of the object that determines if it's "a square".

I guess if you want to take the abstraction even further you wouldn't even say you have a quadrilateral but that you have a polygon or even just a shape.


Its pretty simple :) The more 'base' the class (the first in the derivation chain) should be the most general.

For example shape -> Rectangle -> Square.

Here a square is a special case of a rectangle (with constrained dimensions) and a Rectangle is a special case of a shape.

Said another way - use the "is a" test. A squire is a rectangle. But a rectange is not always a square.

참고URL : https://stackoverflow.com/questions/1030521/is-deriving-square-from-rectangle-a-violation-of-liskovs-substitution-principle

반응형