2016-10-31 2 views
3

하나는 : 하위 유형에왜 Liskov Substitution Principle은 반공 변성이 필요한가? <a href="https://en.wikipedia.org/wiki/Liskov_substitution_principle" rel="nofollow">Liskov Substitution Principle</a>가 파생 클래스에서 메소드 서명에 부과 규칙

Contravariance 방법의 인수.

제대로 이해했다면 파생 클래스의 재정의 함수는 반올림 인수 (Supertype 인수)를 허용해야합니다. 그러나 나는이 규칙 뒤에있는 이유를 이해할 수 없다. LSP는 추상화를 달성하기 위해 유형을 수퍼 유형이 아닌 유형과 동적으로 바인딩하기 때문에 파생 클래스의 메소드 인수로 수퍼 유형을 허용하는 것은 나에게 상당히 혼란 스럽다. 내 질문은 :

  • 왜 LSP는/수 연료 소모량을 overridding 파생 클래스의에서 Contravariant 인수가 필요합니다?
  • 데이터/프로 시저 추상화를 달성하기 위해 Contravariance 규칙이 도움이되는 방법은 무엇입니까?
  • 반 가상어 매개 변수를 파생 클래스의 재정의 된 메서드에 전달해야하는 실제 사례가 있습니까? LSP의 말씀 다음 여기에

답변

2

은은 "파생 된 개체"는 "기본 개체"의 교체로 사용할 수 있습니다.

것은의 당신의 기본 오브젝트는 방법이 있다고 가정 해 봅시다 : 여기

class BasicAdder 
{ 
    Anything Add(Number x, Number y); 
} 

// example of usage 
adder = new BasicAdder 

// elsewhere 
Anything res = adder.Add(integer1, float2); 

를, "번호는"숫자 같은 데이터 유형에 대한 기본 유형의 아이디어, 정수, 수레, 복식 등 같은 건입니다 즉 C++에 존재하지만, 여기에서는 특정 언어에 대해 논의하지 않습니다. 마찬가지로, 예를 들어, "Anything"은 모든 유형의 제한없는 값을 나타냅니다.

class ComplexAdder 
{ 
    Complex Add(Complex x, Complex y); 
} 

// example of usage 
adder = new ComplexAdder 

// elsewhere 
Anything res = adder.Add(integer1, float2); // FAIL 

따라서, 우리가 파산 LSP :

의이 복잡한 사용하는 "전문"입니다 파생 객체를 생각해 보자는 원래 개체에 대한 대체로서 사용할 수 없습니다, 받아 들일 수 없기 때문에 실제로 복합 매개 변수가 필요하기 때문에 integer1, float2 매개 변수가 필요합니다.

반면에 공변 반환 유형은 OK입니다. 반환 유형은 복소수로 복소수는 Anything입니다.

이제

,의 다른 경우를 생각해 봅시다 : 기존의 객체를 사용하고 누구든지, 이제도에 아무런 변화 영향뿐만 아니라 새 개체를 사용할 수 있기 때문에

class SupersetComplexAdder 
{ 
    Anything Add(ComplexOrNumberOrShoes x, ComplexOrNumberOrShoes y); 
} 

// example of usage 
adder = new SupersetComplexAdder 

// elsewhere 
Anything res = adder.Add(integer1, float2); // WIN 

지금은 모든 것이 OK입니다 사용 시점.

물론 숫자와 관련하여 또는 일부 자동 유형 변환과 같은 "결합"또는 "수퍼 집합"유형을 항상 생성 할 수있는 것은 아닙니다. 그렇지만 특정 프로그래밍 언어에 대해 이야기하는 것이 아닙니다. 전반적인 아이디어가 중요합니다.

그것은 또한 당신이 반드시 클래스/메소드 서명 수준에서 LSP에 부합하는 것 같습니다

class SmartAdder 
{ 
    Anything Add(Anything x, Anything y) 
    { 
     if(x is not really Complex) throw error; 
     if(y is not really Complex) throw error; 

     return complex-add(x,y) 
    } 
} 

을 준수 또는 다양한 "수준"에서 LSP를 깰 수 있다는 것을 주목할 가치가있다. 그러나 그것은 무엇입니까? 종종 그렇지 않지만, 그것은 많은 것들에 달려 있습니다.

Contravariance 규칙이 데이터/프로 시저 추상화를 달성하는 데 도움이됩니다 어떻게?

나에게 분명하다. 당신이 말하는 만들 경우, 의미 구성 요소는 교체/스왑/교환 할 수 있습니다 :

  • BASE : 송장의 계산 합 순진
  • DER-1 : 병렬
  • 에서 멀티 코어에 송장의 계산 합 DER-2 : 상세한 로깅

와 인보이스의 합을 계산 한 후, 새로 추가 invoi의

  • 계산 합 다른 통화로 표시합니다.

EUR 및 GBP 입력 값을 처리합니다. 예전 통화의 입력 값은 어떻습니까? 이를 생략하면 새 구성 요소 이 아닌 오래된 구성 요소로 대체됩니다. 이전 구성 요소를 꺼내서 새 구성 요소를 꽂을 수 없으며 모든 것이 좋기를 바랍니다. 시스템의 다른 모든 것들은 USD 값을 입력으로 보낼 수 있습니다.

BASE에서 파생 된 새 구성 요소를 만들면 BASE가 이전에 필요했던 곳이면 어디서나 사용할 수 있다고 가정하는 것이 안전해야합니다. 어떤 장소가 BASE를 필요로하지만 DER-2가 사용 되었다면, 우리는 거기에 새로운 장소를 연결할 수 있어야합니다. 이것은 LSP입니다. 우리가 무언가가 깨진 수없는 경우

  • 사용 did't 중 하나 장소는 BASE는하지만 더는
  • 하거나 구성 요소가 는 BASE을지지 않습니다 (참고 필요한 실제로는-A가 필요 문구)

이제는 아무 것도 고장 나지 않으면 USD 또는 GBP 또는 단일 코어 또는 멀티 코어가 있는지 여부에 관계없이 하나를 취하여 다른 것으로 교체 할 수 있습니다. 이제 한 단계 위의 큰 그림을 보면서 더 이상 특정 유형의 통화를 신경 쓸 필요가 없다면 큰 그림을 더 쉽게 추상화하고 구성 요소를 내부적으로 처리해야합니다. 어쩐지.

그 데이터에 도움이 같은 하락하지 않는 경우/프로 시저 추상화 한 후 반대의 경우를 보면 : BASE에서 파생 된 구성 요소가 LSP을 준수하지 않은 경우 합법적으로 USDs의 값이 도착했을 때

, 다음은 오류를 발생 할 수 있습니다. 또는 더 나쁜 경우, 통지하지 않고 GBP로 처리 할 것입니다. 문제가있다.새로운 구성 요소를 수정하거나 (BASE의 모든 요구 사항을 준수하기 위해) 새이 규칙을 따르도록 다른 이웃 구성 요소를 변경해야합니다 (예 : EUR가 아닌 USD를 사용하거나 Adder가 예외를 throw 함). 큰 그림에 물건을 추가하여 작업하십시오. 즉, 구식 데이터를 감지하고 이전 구성 요소로 리디렉션하는 분기를 추가하십시오. 우리는 단지 이웃들에게 복잡성을 "누설했다"(그리고 아마도 우리는 SRP를 깨뜨릴 수밖에 없었다) 또는 "큰 그림"을 더 복잡하게 만들었다 (더 많은 어댑터, 조건, 브랜치, ..).

+0

자세한 설명 주셔서 감사합니다. 그러나, Anything res = adder.Add (integer1, float2); // WIN' Add 메소드가'SupersetComplexAdder' 클래스의 인자로'Number'를 가지고 있다면 참일 수 있습니다. 'BasicAdder'가'Add' 메쏘드에서 Number 형이나 그 subtype을 인자로 기대하지 않는다는 것을 분명히 알았 기 때문에, 상속받은 클래스 내에서 supertype을 인자로 사용하면 호출자에게 특별한 기능이 없습니다. – Mac

+0

하위 유형이 공변 인수를 허용하더라도이 경우 호출자 코드 (Client)는 'SupersetComplexAdder'객체를 다른 하위 유형 'BasicAdder'로 대체하는 조항을 잃어 버렸습니다. 이제 코드는 'SupersetComplexAdder '. 그리고 이것 자체가'BasicAdder'를위한 LSP를 깨뜨립니다. 나는 LSP가 여전히'SupersetComplexAdder'에 대해 사실 인 것으로 동의 할지라도. – Mac

+0

답을 다시 읽은 후에 LSP에서 공분산을 지원하는 중요성에 대해 아주 명확한 그림을 얻었습니다. :) 다시 한번 감사드립니다. – Mac

3

"메소드 인수의 반공 변"은 간결 할 수 있지만 모호합니다. 다음의 예제로 사용하자 :

class Base { 
    abstract void add(Banana b); 
} 

class Derived { 
    abstract void add(Xxx? x); 
} 

지금, "방법 인자의 contravariance는" Derived.add이 유형 Banana이나 슈퍼, ? super Banana 같은 것을 가지는 모든 객체에 동의해야한다는 것을 의미 할 수있다. 이것은 입니다. LSP 규칙의 해석이 잘못되었습니다.

실제 해석은 다음과 같습니다 "Derived.add 어느 단지 Base에서 같은 유형 Banana, 또는 Banana 같은 Fruit 등의 일부 슈퍼에 선언해야합니다." 선택한 상위 유형은 귀하에게 달려 있습니다.

이 해석을 사용하면 규칙이 완벽하게 적용된다는 것을 알 수 있습니다. 하위 클래스는 상위 API와 호환되지만 선택적으로 기본 클래스가 지원하지 않는 추가 사례도 포함합니다. 따라서 기본 클래스에 대해 LSP로 대체 할 수 있습니다.

실제로이 하위 클래스의이 확대 유형이 유용한 많은 예제가 없습니다. 나는 이것이 대부분의 언어가 그것을 구현하는 것을 괴롭히지 않는 이유라고 생각한다. 엄격하게 동일한 유형을 요구하면 LSP도 유지되지만 LSP를 달성하는 동안 가질 수있는 모든 유연성을 제공하지는 않습니다.

+0

당신의 귀중한 입력 주셔서 감사합니다.하지만, 여전히 내 질문에 그 기본 클래스 자체가 그것의 우선 순위 메서드에있는 인수의 상위 형식에 대해 귀찮게하지 않는 경우 기본 클래스는 클라이언트가 원하는 명확한 표시입니다 호출자)는 인수의 유형/부속 유형만을 처리합니다. 그러한 경우 파생 클래스에서 인수로 슈퍼 유형을 제공하면 파생 클래스 객체에서 해당 메소드를 수퍼 클래스 참조로 호출하는 호출자에게 새로운 조항을 제공하지 않습니다. – Mac

+0

예를 들어, 호출자는 항상'base.callMe (Number num); 또는'base.callMe (Integer int);를 호출하지만'base.callMe (Object obj);는 절대 사용하지 않을 것입니다. – Mac

+0

공변 리턴 타입과 마찬가지로, 이것은 하위 유형의 클라이언트 만 서비스합니다. –