국가 패턴이 Liskov 대체 원칙을 위반합니까?


17

이 이미지는 도메인 기반 디자인 및 패턴 적용 : C # 및 .NET의 예제 포함

여기에 이미지 설명을 입력하십시오

이는 대한 클래스 다이어그램 주 패턴 A가 SalesOrder수명 시간 동안 여러 가지 상태를 가질 수 있습니다. 다른 상태 사이에는 특정 전환 만 허용됩니다.

이제 OrderState클래스는 abstract클래스이고 모든 메소드는 서브 클래스로 상속됩니다. Cancelled다른 상태로의 전이를 허용하지 않는 최종 상태 인 서브 클래스 를 고려한다면 , override이 클래스의 모든 메소드에서 예외를 발생시켜야합니다.

Sublcass가 부모의 행동을 변경해서는 안되기 때문에 Liskov의 대체 원칙을 위반하지 않습니까? 추상 클래스를 인터페이스로 변경하면이 문제가 해결됩니까?
이 문제를 어떻게 해결할 수 있습니까?


OrderState를 기본적으로 모든 메소드에서 "NotSupported"예외를 발생시키는 구체적인 클래스로 변경 하시겠습니까?
James

이 책을 읽지 못했지만 OrderState에 Cancel ()이 포함되어 있고 Canceled 상태가 있다는 것이 이상하게 보입니다.
Euphoric

@Euphoric 왜 이상해? 호출 cancel()하면 상태가 취소됨으로 변경됩니다.
Songo

어떻게? SalesOrder의 상태 인스턴스가 변경되어야하는 경우 OrderState에서 취소를 호출하는 방법 전체 모델이 잘못되었다고 생각합니다. 전체 구현을보고 싶습니다.
Euphoric

답변:


3

이 특정 구현입니다. 추상 구현자가 아닌 상태를 구체적인 클래스로 만들면 이것에서 벗어날 수 있습니다.

그러나 당신이 언급하고있는 상태 패턴은 효과적으로 상태 머신 디자인이라는 것이 일반적으로 내가 본 방식과는 다른 의견입니다. 이러한 상태 관리 패턴이 시스템의 다른 많은 부분의 현재 상태를 알기위한 중앙 저장소가되기 때문에이를 단일 책임 원칙을 위반하는 것으로 간주 할 수있는 충분한 근거가 있다고 생각합니다. 중앙 집중화되는이 상태 관리 부분은 합리적으로 조정하기 위해 시스템의 여러 부분과 관련된 비즈니스 규칙을 요구하지 않는 경우가 더 많습니다.

상태를 관리하는 시스템의 모든 부분이 서로 다른 서비스, 서로 다른 시스템의 서로 다른 프로세스, 각 장소의 상태를 자세히 설명하는 중앙 상태 관리자가 전체 분산 시스템에 효과적으로 병목 현상을 일으켜 병목 현상이 징후라고 생각한다고 상상해보십시오. SRP 위반 및 일반적으로 잘못된 설계.

대조적으로, 모델이 자신을 처리하는 방법을 알고있는 MVC 패턴의 Model 객체에서보다 객체를보다 지능적으로 만들도록 제안합니다. 내부 작업 또는 이유를 관리하기 위해 외부 조정자가 필요하지 않습니다.

객체 내부에 이와 같은 상태 패턴을 넣어서 자체 관리하는 것만으로도 객체를 너무 크게 만드는 것처럼 느껴집니다. 워크 플로는 다른 개체의 흐름 또는 자체의 지능 흐름을 관리하는 단일 오케스트레이션 상태가 아니라 내가 말하고자하는 다양한 자체 책임 개체의 구성을 통해 수행해야합니다.

그러나 그 시점에서이 기술보다 더 예술 그리고 그것은 확실히 말했다 것들 중 일부에 대한 접근 방식을 주관적되도록 원칙은 좋은 가이드이며, 예 구현하면 목록 입니다 LSP를 위반하지만, 수 없습니다 보정 할 수있다. 이러한 성격의 패턴을 사용할 때 SRP에 매우주의하면 안전 할 것입니다.


기본적으로 큰 문제는 객체 방향이 새 클래스를 추가하는 데는 좋지만 새 메서드를 추가하기 어렵다는 것입니다. 그리고 상태의 경우 말했듯이 새로운 상태로 코드를 매우 자주 확장해야 할 필요는 없습니다.
hugomg

3
@Jimmy Hoffa 죄송 합니다만, "추상 구현자가 아닌 구체적인 클래스를 만들면 여기서 벗어날 수 있습니다." ?
Songo

@ Jimim : 각 구성 요소가 자체 상태에 대해서만 알도록하여 상태 패턴없이 상태를 구현하려고했습니다. 결과적으로 구현 및 유지 관리가 훨씬 더 복잡해 지도록 많은 변경이 이루어졌습니다. 즉, 상태 디자인 패턴 대신 상태 머신 (이상적으로 다른 사람의 라이브러리를 블랙 박스로 취급하도록 강요)을 사용하는 것 같습니다. edge)는 상태 패턴의 많은 이점을 제공하면서 개발자가이를 악용하려는 시도에 적대적입니다.
Brian

8

Sublcass는 부모의 행동을 변경해서는 안됩니까?

그것은 LSP에 대한 일반적인 오해입니다. 서브 클래스는 부모 유형에 충실한 한 부모의 동작을 변경할 수 있습니다.

Wikipedia에 대한 자세한 설명이 있으며 LSP를 위반할 사항을 제안합니다.

하위 유형이 충족해야하는 여러 가지 행동 조건이 있습니다. 이것들은 계약 방법론에 의한 디자인의 용어와 유사한 용어에 자세히 설명되어 있으며, 계약이 상속과 상호 작용하는 방법에 대한 몇 가지 제한을 초래합니다.

  • 하위 유형에서는 전제 조건을 강화할 수 없습니다.
  • 하위 유형에서는 사후 조건을 약화시킬 수 없습니다.
  • 상위 유형의 변형은 하위 유형으로 유지되어야합니다.
  • 히스토리 제한 사항 ( "히스토리 규칙"). 객체는 그 방법 (캡슐화)을 통해서만 수정 가능한 것으로 간주됩니다. 서브 타입은 수퍼 타입에 존재하지 않는 메소드를 도입 할 수 있기 때문에, 이러한 메소드의 도입은 수퍼 타입에 허용되지 않는 서브 타입의 상태 변경을 허용 할 수 있습니다. 히스토리 제한은이를 금지합니다. Liskov와 Wing이 소개 한 새로운 요소였습니다. MutablePoint를 ImmutablePoint의 하위 유형으로 정의하여이 제약 조건을 위반하는 것을 예로들 수 있습니다. 이것은 불변 지점의 이력에서 생성 후 상태가 항상 동일하므로 일반적으로 MutablePoint의 이력을 포함 할 수 없기 때문에 이력 제약 조건을 위반하는 것입니다. 그러나 하위 유형에 추가 된 필드는 수퍼 유형 메소드를 통해 관찰 할 수 없으므로 안전하게 수정할 수 있습니다.

개인적으로, 나는 이것을 간단히 기억하는 것이 더 쉽다는 것을 안다 : 타입 A가있는 메소드의 매개 변수를보고 있다면 하위 유형 B를 전달하는 누군가 나를 놀라게 할 것인가? 그렇다면 LSP를 위반 한 것입니다.

예외를 던지는 것은 놀라운 일입니까? 실제로는 아닙니다. OrderState에서 Ship 메소드를 호출하든 Granted 또는 Shipped에서 호출하든 관계없이 언제든지 발생할 수 있습니다. 그래서 나는 그것을 설명해야하며 실제로 LSP의 위반이 아닙니다.

, 나는이 상황을 처리하는 더 좋은 방법이 있다고 생각합니다. C #으로 이것을 작성한다면 인터페이스를 사용하고 메소드를 호출하기 전에 인터페이스의 구현을 확인합니다. 예를 들어, 현재 OrderState가 IShippable을 구현하지 않으면 Ship 메소드를 호출하지 마십시오.

그러나이 특정 상황에서는 State 패턴을 사용하지 않을 것입니다. 상태 패턴은 이와 같은 도메인 개체의 상태보다 응용 프로그램의 상태에 훨씬 더 적합합니다.

간단히 말해서, 이것은 상태 패턴에 대해 잘 고안되지 않은 예이며 주문 상태를 처리하는 특히 좋은 방법은 아닙니다. 그러나 LSP를 위반하지는 않습니다. 그리고 국가 패턴 자체는 확실히 그렇지 않습니다.


LSP와 놀라움 / 놀람의 원리를 혼동하는 것처럼 들리지만, LSP는 경계를 위반하는 경계가 더 명확하다고 생각합니다. ; 즉, 각 사람은 다음과 다른 것을 기대합니다. LSP는 공급자가 제공 한 계약 및 잘 정의 된 보증을 기반으로하며 소비자가 추측 한 주관적으로 평가 된 기대치는 아닙니다.
Jimmy Hoffa

@JimmyHoffa : 맞습니다. 그래서 LSP를 기억하는 더 간단한 방법을 말하기 전에 정확한 의미를 설명했습니다. 그러나 "놀람"이 무엇을 의미하는지에 대한 의문이있는 경우, 되돌아 가서 LSP의 정확한 규칙을 확인합니다 (실제로 마음에들 수 있습니까?). 기능을 대체하는 예외 (또는 그 반대)는 위의 특정 조항을 위반하지 않기 때문에 어렵다. 이는 아마도 완전히 다른 방식으로 처리되어야한다는 단서 일 것이다.
pdr

1
예외는 내 마음에 계약에 정의 된 예상 조건입니다 .NetworkSocket을 생각하십시오. 개방되지 않은 경우 전송시 예상 예외가있을 수 있습니다. 구현이 닫힌 소켓에 대해 예외를 throw하지 않으면 LSP를 위반하는 것입니다 계약에 반대가 있고 하위 유형에서 예외가 발생하면 LSP를 위반 한 것입니다. LSP에서 예외를 분류하는 방법은 다소 있습니다. 규칙을 기억하는 것과 관련하여; 나는 이것에 틀릴 수 있고 자유롭게 말해주지 만 LSP 대 POLA에 대한 나의 이해는 위의 나의 의견의 마지막 문장에 정의되어있다.
Jimmy Hoffa

1
@ JimimHoffa : POLA와 LSP가 원격으로 연결되어 있다고 생각한 적이 없었습니다 (UI 용어로 POLA를 생각합니다). 이제 지적 했으므로 이제는 종류가 있습니다. 나는 그들이 충돌하고 있는지, 또는 하나가 다른 것보다 더 주관적이라고 확신하지 않습니다. LSP는 소프트웨어 엔지니어링 용어로 POLA에 대한 초기 설명이 아닙니까? Ctrl-C가 복사되지 않을 때 좌절감을 느끼는 것과 같은 방식으로 (POLA) ImmutablePoint가 실제로 MutablePoint 서브 클래스 (LSP)이기 때문에 ImmutablePoint가 변경 가능한 경우에도 좌절합니다.
pdr

1
CircleWithFixedCenterButMutableRadius대한 소비자 계약에서 X가 Y 대신 Y를 자유롭게 대체 할 수 있고, 그 반대의 경우도 가능하므로 그러한 대체가 문제를 야기 할 수있는 방식으로 클래스를 사용하지 않아야 ImmutablePoint한다고 규정하는 경우 가능한 LSP 위반으로 간주 X.equals(Y)합니다. 유형은 equals모든 인스턴스가 별개로 비교 되도록 합법적으로 정의 할 수 있지만 이러한 동작은 유용성을 제한 할 수 있습니다.
supercat

2

(C # 관점에서 작성되었으므로 확인 된 예외는 없습니다.)

LSP대한 Wikipedia 기사에 따르면 LSP 의 조건 중 하나는 다음과 같습니다.

하위 유형 자체가 수퍼 유형의 메소드에 의해 발생 된 예외의 하위 유형 인 경우를 제외하고 하위 유형의 메소드에 의해 새로운 예외가 발생해서는 안됩니다.

그걸 어떻게 이해해야합니까? 수퍼 타입의 메소드가 추상적 일 때 정확히“슈퍼 타입의 메소드에 의해 발생 된 예외”는 무엇입니까? 나는 이것이 수퍼 타입의 메소드에 의해 발생 될 수있는 예외로 문서화 된 예외라고 생각 합니다.

이것이 의미하는 것은 OrderState.Ship()" InvalidOperationException이 상태가 현재 상태에서 지원되지 않는 경우 발생합니다 "와 같이 문서화되어 있다면이 디자인이 LSP를 깨뜨리지 않는다고 생각합니다. 반면에, 수퍼 타입 ​​메소드가 이런 식으로 문서화되지 않으면 LSP가 위반됩니다.

그러나 이것이 좋은 디자인임을 의미하는 것은 아니며, 정상적인 제어 흐름에는 예외를 사용해서는 안됩니다. 또한 실제 응용 프로그램에서는 UI에서 "Ship"버튼을 비활성화하는 등 의 작업을 수행 하기 전에 작업을 수행 할 수 있는지 알고 싶을 것입니다 .


실제로 이것이 바로 저를 생각하게 한 요점입니다. 하위 클래스가 부모에 정의되지 않은 예외를 throw하면 LSP를 위반해야합니다.
Songo

예외가 확인되지 않은 언어에서는 예외를 던지는 것이 LSP를 위반하지 않는다고 생각합니다. 언제 어디서나 예외가 발생할 수있는 것은 언어 자체의 개념 일뿐입니다. 따라서 모든 클라이언트 코드가 준비되어 있어야합니다.
Zapadlo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.