나는 Liskov 대체 원칙에 대한 위반 가능성에 대해이 매우 투표가 많은 질문 을 따랐습니다. 나는 Liskov 대체 원칙이 무엇인지 알고 있지만, 여전히 내 마음에 분명하지 않은 것은 개발자가 객체 지향 코드를 작성하는 동안 원칙에 대해 생각하지 않으면 잘못 될 수 있다는 것입니다.
나는 Liskov 대체 원칙에 대한 위반 가능성에 대해이 매우 투표가 많은 질문 을 따랐습니다. 나는 Liskov 대체 원칙이 무엇인지 알고 있지만, 여전히 내 마음에 분명하지 않은 것은 개발자가 객체 지향 코드를 작성하는 동안 원칙에 대해 생각하지 않으면 잘못 될 수 있다는 것입니다.
답변:
나는 그 질문에 아주 잘 언급되어 있다고 생각합니다.
이제 Task에서 Close ()를 호출하면 시작 상태의 ProjectTask 인 경우 기본 Task 인 경우 호출이 실패 할 가능성이 있습니다.
당신이 할 것이라고 상상해보십시오 :
public void ProcessTaskAndClose(Task taskToProcess)
{
taskToProcess.Execute();
taskToProcess.DateProcessed = DateTime.Now;
taskToProcess.Close();
}
이 메소드에서 때때로 .Close () 호출이 발생하기 때문에 파생 유형의 구체적인 구현을 기반으로 Task에 하위 유형이없는 경우이 메소드가 작성되는 방식에서이 메소드의 동작 방식을 변경해야합니다. 이 방법을 전달했습니다.
liskov 대체 위반으로 인해 형식을 사용하는 코드는 파생 형식의 내부 작업에 대해 명시 적으로 알고 있어야이를 다르게 처리 할 수 있습니다. 이것은 코드를 밀접하게 결합하며 일반적으로 구현을 일관되게 사용하기 어렵게 만듭니다.
기본 클래스에 정의 된 계약을 이행하지 않으면 결과가 꺼져있을 때 자동으로 실패 할 수 있습니다.
이 중 하나라도 보유하지 않으면 발신자는 예상치 못한 결과를 얻을 수 있습니다.
인터뷰 질문의 연대기에서 고전적인 경우를 고려하십시오. Ellipse에서 Circle을 파생했습니다. 왜? 물론 원은 타원이기 때문에!
타원에는 두 가지 기능이 있습니다.
Ellipse.set_alpha_radius(d)
Ellipse.set_beta_radius(d)
분명히 원의 반지름이 균일하기 때문에 원에 대해 다시 정의해야합니다. 두 가지 가능성이 있습니다.
대부분의 OO 언어는 두 번째 언어를 지원하지 않으며 정당한 이유가 있습니다. Circle이 더 이상 Circle이 아니라는 것은 놀라운 일입니다. 첫 번째 옵션이 가장 좋습니다. 그러나 다음 기능을 고려하십시오.
some_function(Ellipse byref e)
some_function이 e.set_alpha_radius를 호출한다고 상상해보십시오. 그러나 e는 실제로 Circle이기 때문에 놀랍게도 베타 반경도 설정되어 있습니다.
그리고 여기에는 대체 원칙이 있습니다 : 서브 클래스는 수퍼 클래스를 대신 할 수 있어야합니다. 그렇지 않으면 놀라운 일이 발생합니다.
평신도의 말로 :
코드에는 수많은 CASE / 스위치 절이 있습니다.
이러한 CASE / 스위치 조항 중 하나는 때때로 새로운 사례를 추가해야합니다. 즉, 코드 기반은 확장 성 및 유지 관리가 쉽지 않습니다.
LSP를 사용하면 코드가 하드웨어처럼 작동 할 수 있습니다.
기존의 외부 스피커와 새로운 외부 스피커가 동일한 인터페이스를 사용하기 때문에 iPod이 원하는 기능을 잃지 않으면 서 서로 교환 할 수 있기 때문에 새로운 외부 스피커 쌍을 구입했기 때문에 iPod을 수정할 필요가 없습니다.
typeof(someObject)
"허용 된"것을 결정하기 위해 전환하는 것을 언급한다면 , 확실히, 그것은 또 다른 반 패턴입니다.
java의 UndoManager 로 실제 예제를 제공합니다.
그것은 상속에서 AbstractUndoableEdit
그 계약 지정은이 개 상태를 가지고 (실행 취소 및 재실행) 및 싱글 호출로 그들 사이에 갈 수 undo()
및redo()
그러나 UndoManager에는 더 많은 상태가 있으며 실행 취소 버퍼처럼 작동합니다 (각 호출은 undo
일부 편집 을 취소 하지만 모든 조건 을 취소 하여 사후 조건을 약화시킵니다)
이로 인해 호출하기 전에 UndoManager를 CompoundEdit에 추가하고 end()
해당 CompoundEdit에서 undo를 호출하면 undo()
편집이 부분적으로 취소 된 상태로 남아있는 각 편집 을 호출하게 됩니다.
나는 그것을 UndoManager
피하기 위해 내 자신 을 굴렸다 (아마도 이름을 바꿔야한다 UndoBuffer
)
예 : UI 프레임 워크를 사용하고 있으며 Control
기본 클래스 를 서브 클래 싱하여 고유 한 사용자 정의 UI 제어를 작성합니다 . Control
기본 클래스는 방법을 정의 한다 중첩 컨트롤 컬렉션 (있는 경우)를 리턴한다. 그러나 실제로 미국 대통령의 생년월일 목록을 반환하는 방법을 재정의합니다.getSubControls()
그렇다면 무엇이 잘못 될 수 있습니까? 예상대로 컨트롤 목록을 반환하지 않기 때문에 컨트롤 렌더링에 실패합니다. UI가 충돌 할 가능성이 높습니다. 당신은하는 계약 위반 컨트롤의 서브 클래스가 준수 할 것으로 예상된다.
모델링 관점에서 볼 수도 있습니다. 클래스의 인스턴스가 클래스의 인스턴스라고 말할 때 " A
클래스의 인스턴스의 B
관찰 가능한 동작은 클래스의 인스턴스의 A
관찰 가능한 동작으로 분류 될 수도 있습니다 B
"(클래스 B
가 덜 구체적 일 경우에만 가능함) 클래스 A
.)
따라서 LSP를 위반한다는 것은 디자인에 모순이 있음을 의미합니다. 객체에 대한 일부 범주를 정의한 다음 구현에서 해당 범주를 존중하지 않으면 무언가 잘못되어야합니다.
"이 상자에는 파란 공만 들어 있습니다"라는 태그가있는 상자를 만든 다음 빨간 공을 던지는 것과 같습니다. 잘못된 정보가 표시되는 경우 이러한 태그는 어떻게 사용됩니까?
최근에 주요 Liskov 위반자가있는 코드베이스를 상속했습니다. 중요한 수업에서. 이로 인해 엄청난 고통이 생겼습니다. 이유를 설명하겠습니다.
나는 Class A
에서 유래했다 Class B
. Class A
및 Class B
특성의 무리 공유 Class A
자신의 구현을 재정의. 설정 또는 점점 Class A
재산은 설정하거나에서 동일한 속성을 얻기에 다른 효과가 있습니다 Class B
.
public Class A
{
public virtual string Name
{
get; set;
}
}
Class B : A
{
public override string Name
{
get
{
return TranslateName(base.Name);
}
set
{
base.Name = value;
FunctionWithSideEffects();
}
}
}
이것이 .NET에서 번역을 수행하는 완전히 끔찍한 방법이라는 사실을 제외하면이 코드에는 다른 많은 문제가 있습니다.
이 경우 Name
여러 곳에서 색인 및 흐름 제어 변수로 사용됩니다. 위의 클래스는 코드베이스 전체에서 원시 및 파생 형식으로 흩어져 있습니다. 이 경우 Liskov 대체 원칙을 위반하면 기본 클래스를 사용하는 각 함수에 대한 모든 단일 호출 컨텍스트를 알아야합니다.
코드 사용은 모두의 객체 Class A
와 Class B
나는 간단하게 만들 수 있으므로, Class A
사용하는 사람들을 강제로 추상 Class B
.
작동하는 매우 유용한 유틸리티 기능과 작동하는 Class A
매우 유용한 유틸리티 기능이 있습니다 Class B
. 이상적으로는에 작동 할 수있는 유틸리티 기능을 사용할 수 있도록하고 싶습니다 Class A
에를 Class B
. LSP를 위반하지 않는 경우 많은 기능을 Class B
쉽게 수행 할 수 있습니다 Class A
.
이것에 대한 최악의 점은 전체 응용 프로그램 이이 두 클래스에 달려 있고 항상 두 클래스 모두에서 작동하며이를 변경하면 백 가지 방식으로 중단 되므로이 특정 사례는 실제로 리팩토링하기가 어렵다는 것입니다. 어쨌든).
이 문제를 해결하기 위해해야 할 일은 NameTranslated
속성을 만드는 것 Class B
입니다. Name
속성 의 버전이 되고 파생 Name
속성에 대한 모든 참조를 새 NameTranslated
속성 을 사용 하도록 매우 신중하게 변경해야 합니다. 그러나 이러한 참조 중 하나라도 잘못하면 전체 응용 프로그램이 손상 될 수 있습니다.
코드베이스에 단위 테스트가 없기 때문에 개발자가 직면 할 수있는 가장 위험한 시나리오에 가깝습니다. 위반을 변경하지 않으면 각 방법에서 어떤 유형의 물체가 작동하는지 추적하는 데 많은 양의 정신 에너지를 소비해야하며 위반을 해결하면 전체 제품이 부적절하게 폭발 할 수 있습니다.
BaseName
및 TranslatedName
액세스 클래스-A 스타일 모두 Name
와 클래스 B의 의미를? 그러면 Name
변수 유형에 대한 액세스 시도가 B
컴파일러 오류와 함께 거부되므로 모든 참조가 다른 형식 중 하나로 변환되었는지 확인할 수 있습니다.