2013-05-29 2 views
6

Square에 Rectangle 클래스의 상속 클래스를 만드는 것이 LSP (Liskov 대체 원칙)를 위반한다고 말하는 관행에 관한 기사를 읽은 적이 있습니다. 아직 이해가 안되네요. 루비에서 예제 코드를 만들었습니다.사각형 및 사각형 상속에 어떤 문제가 있습니까?

class Rectangle 
    attr_accessor :width, :height 
    def initialize(width, height) 
     @width = width 
     @height = height 
    end 
end 

class Square < Rectangle 
    def initialize(length) 
     super(length, length) 
    end 
    def width=(number) 
     super(number) 
     @height = number 
    end 

    def height=(number) 
     super(number) 
     @width = number 
    end 
end 


s = Square.new(100) 

s.width = 50 

puts s.height 

아무도 저에게 무슨 문제가 있다고 말할 수 있습니까?

+0

울퉁불퉁 한 공주님? http://www.youtube.com/watch?v=pJTrD3R5cj0 – paxdiablo

+0

와우, 그게 흥미 롭 네요.하지만 꽤 이해가 안되네. – mko

+1

yozloy, 사과, 나는 네가 의미하는 바를 설명하고 싶을지도 모른다고 지적했다. LSP를 인식하지 못하는 사람들은 검색 할 필요가 없습니다. – paxdiablo

답변

4

Liskov는 "본질"보다는 동작을 기반으로 상속을 통해 할 수있는 일을 제한하는 것처럼 보입니다. 내 견해로, 상속은 항상 "하나의 관계"가 아니라 "정확히 같은 역할"을 의미합니다.

the wikipedia article이이 정확한 예제를 사용하여, 일부에 의해 나쁜 것으로 간주되는 이유에 관한 내용이수록했다 가졌 :

LSP를 위반하는 전형적인 예는 사각형에서 파생 광장 클래스 getter 메소드와 setter 메소드가 width와 height 모두에 대해 존재한다고 가정합니다.

Square 클래스는 항상 너비가 높이와 같다고 가정합니다. Rectangle이 예상되는 컨텍스트에서 Square 개체를 사용하면 Square의 크기를 독립적으로 수정할 수 없으므로 (또는 오히려 변형해서는 안 됨) 예상치 못한 동작이 발생할 수 있습니다.

이 문제는 쉽게 해결할 수 없습니다. 스퀘어 클래스의 setter 메서드를 수정하여 Square 불변량을 유지 (즉, 차원을 동일하게 유지)하면 이러한 메서드는 해당 사각형의 사후 조건을 약화 (위반)합니다. 치수를 독립적으로 수정할 수있는 사각형 세터.

그래서, 해당 Rectangle 코드와 함께 코드를보고 : 출력이 오른쪽에 왼쪽에 50과 100이 될 것

s = Square.new(100)   r = Rectangle.new(100,100) 
s.width = 50     r.width = 50 
puts s.height     puts r.height 

.

그러나, 내보기에, 기사에서 중요한 비트입니다 :이처럼 LSP의

위반, 는 또는는 연습 문제에 따라하지 않을 수도 있습니다 LSP를 위반하는 클래스를 사용하는 코드가 실제로 예상하는 사후 조건 또는 불변 조건

즉, 코드 을 사용하면을 사용하여 클래스가 동작을 인식하므로 아무런 문제가 없습니다.

결론은 사각형 (LSP)이 관점은 당신의 Rectangle s입니다 무엇 Liskov 대체 원칙에서 잘못

+0

감사합니다. 'Rectangle' 코드에서 얻지 못하는 한 가지는'r = Rectangle.new (100)'라인입니다.'r = Rectangle.new (100, 100)'을 의미합니까? – mko

+0

@yozloy, 네, 죄송합니다. 그것은 cut'n'paste 오류였습니다. 단 하나의 인수 Rectangle 생성자라고 주장 할 수 있었지만 정사각형을 만들었습니다 :-) 지금 수정되었습니다. – paxdiablo

+0

@ paxdiable 잡았어! 's.height'의 출력과'r.height'의 출력에 대해 이야기 할 때, 나는 '50', '100'이 올바른 결과물이라고 생각합니다. – mko

2

:-) 사각형의 느슨한 충분히 정의, 사각형의 부분 집합이다 Square은 변경 가능합니다. 즉, 서브 클래스의 setter를 명시 적으로 다시 구현해야하고 상속의 이점을 상실해야합니다. Rectangle을 불변으로 설정하면 기존 측정 값을 변경하는 대신 다른 Rectangle을 새로 만들려면 LSP를 위반하는 데 문제가 없습니다.

class Rectangle 
    attr_reader :width, :height 

    def initialize(width, height) 
    @width = width 
    @height = height 
    end 

    def area 
    @width * @height 
    end 
end 

class Square < Rectangle 
    def initialize(length) 
    super(length, length) 
    end 
end 

attr_reader을 사용하면 getter가 제공되지만 설정자가 제공되지 않으므로 변경되지 않습니다. 이 구현에서 RectanglesSquares은 모두 height 및 에 대한 가시성을 제공합니다. 정사각형은 항상 동일 할 것이며 영역의 개념은 일관됩니다.

+0

어! 나는 네가 어디서 왔는지 알 수있어 좋은 설명이다. 그러나 이것은 객체 재사용을 다소 어렵게 만든다. 객체의 크기를 조정하려면 속성이 수정 된 새로운 객체를 만들어야하고 이전 객체를 파괴해야합니다. 그것은 당신의 대답을 덜 유효하게 만들지는 않으며, 그것은 단지 내 눈에서 LSP의 유용성을 감소 시킨다는 것입니다. – paxdiablo

+0

@pjs 왜 mutable이 상속의 이점을 잃어 버리십니까? 내 생각에 setter 메소드를 다시 구현하면 Square 클래스는 Rectangle 클래스에 정의 된 메소드를 재사용 할 수있다. – mko

+0

@paxdiablo : 저는 LSP를 옹호하지 않고 단지 그것을 설명하려고 노력하고 있습니다. 그러나, 나는 불변 개체를 보증하는 경향이있다. 치수가 다른 직사각형은 다른 직사각형입니다. 이것은 많은 경우에 훨씬 더 안전하게합니다. 예를 들어, 영역별로 정렬 된 이진 검색 트리에 여러 개의 사각형을 넣는 것을 고려해보십시오. 이제 이들 중 하나의 차원을 변경하면 트리가 갑자기 미래의 액세스에 실패하게됩니다. 그 요소 중 하나가 갑자기 트리의 기본 주문 속성을 위반하게됩니다. 그런 버그는 추적하기가 정말 어려울 수 있습니다. – pjs

0

추상 기본 클래스 또는 인터페이스 (인터페이스 또는 추상 클래스가 LSP와 관련성이없는 구현 세부 사항인지 여부)를 고려하십시오. ReadableRectangle; 읽기 전용 속성 WidthHeight이 있습니다. 그것으로부터 동일한 유형의 ReadableSquare 유형을 도출 할 수 있지만, 계약 상으로 WidthHeight이 항상 동일하다는 것을 보장 할 수 있습니다.

ReadableRectangle에서, 하나는 (생성자의 높이와 폭을 소요하고 HeightWidth 속성은 항상 같은 값을 반환 보장), 및 MutableRectangle 구체적인 유형 ImmutableRectangle을 정의 할 수 있습니다. 높이와 너비를 언제든지 설정할 수있는 구체적인 유형 MutableRectangle을 정의 할 수도 있습니다.

"사각"측면의 경우 ImmutableRectangleReadableSquare을 모두 ImmutableSquare으로 대체해야합니다. 그러나, 은 ReadableSquare [ReadableRectangle으로 대체 할 수있다.] 또한 ImmutableRectangleImmutableSquare으로 대치 할 수 있지만, ImmutableRectangle 유형을 상속 한 값은 제한적일 수있다. ImmutableRectangle이 추상 유형 또는 인터페이스 인 경우 ImmutableSquare 클래스는 치수를 보유하기 위해 두 개 필드 대신 한 필드 만 사용하면됩니다. 두 개의 필드가있는 클래스의 경우 절약하면 별 효과가 없지만 한 필드가있는 클래스는 상상하기 어렵지 않습니다. 저축이 의미있는 분야가 더 많음). 그러나 ImmutableRectangle이 구체 유형이면 모든 파생 유형은 기본의 모든 필드를 가져야합니다.

일부 유형의 사각형은 해당 유형의 사각형으로 대체 할 수 있지만 변경 가능한 사각형은 변경 가능한 사각형으로 대체 할 수 없습니다.