2012-03-21 2 views
3

나는이 상황에 상당히 많이 부딪 치고 아직 받아 들여질 수있는 해결책을 찾지 못한 것 같습니다.다형성 상속 계층 구조에서 유형 안전을 적용하는 모범 사례

한 계층의 메소드가 다른 계층의 일치하는 클래스를 매개 변수로 전달하는 병렬 상속 계층이 자주 있습니다.

다음은이 점을 더 잘 설명하는 예입니다. 과거

abstract class Animal 
{ 
    public virtual void Eat(Food f) 
    { 
    } 
} 

abstract class Food 
{ 
} 

class LionFood : Food 
{ 
} 

class ElephantFood : Food 
{ 
} 

class Lion : Animal 
{ 
    public override void Eat(Food f) 
    { 
     // It is only ever valid to pass LionFood here as the parameter. 
     // passing any other type of Food is invalid and should be prevented 
     // or at least throw an exception if it does happen. 
    } 
} 

, 나는 보통에서 다음과 같이 구현하는 구체적인 클래스 유형을 정의 할 수 있도록 일반적인 기본 클래스 ..

abstract class Animal<T> where T : Food 
{ 
    public abstract void Eat(T f); 
} 

class Lion : Animal<LionFood> 
{ 
    public override void Eat(LionFood f) 
    { 
    } 
} 

을 만들었습니다 먼저이 매우 좋은 해결책이 있기 때문에 것 같다 그것은 컴파일 타임 타입 안전을 제공합니다. 그러나 더 많이 사용할수록, 이런 방식으로 제네릭을 사용하는 것이 사실상 anti-pattern이라고 생각하기 시작합니다. 문제는 Animal 기본 클래스를 다형성 방식으로 사용할 수 없다는 것입니다. 예를 들어 실제 구체적인 유형에 관계없이 모든 유형의 Animal을 처리 할 메소드를 쉽게 작성할 수는 없습니다.

이 제네릭 솔루션을 사용할 때마다 나는 항상 내가 원하는 다형성 동작을 시도하고 제공하기 위해 모든 곳에서 공변 (covariant) 및 반 (contravariant) 인터페이스로 끝나는 것처럼 보입니다. 이것은 손에서 빠져 나오고 올바른 인터페이스를 제공 할 수 없기 때문에 일부 기능을 사용할 수 없습니다. 물론

또 다른 옵션은 제네릭을 사용하고이 같은 먹어 방법 런타임 유형 검사를 수행하지 않는 것입니다 :

public override void Eat(Food f) 
    { 
     if (f.GetType() != typeof(LionFood)) 
     { 
      throw new Exception(); 
     } 
    } 

이 내가 생각없는 것보다는 낫다 그러나 나는 단순히 그것의 큰 팬이 아니에요 컴파일 타임 형 안전성이 부족하기 때문입니다.

그래서 결국 .. 내 질문은 .. 같은 유형의 안전성을 확보하면서 동시에 다형성 행동을 제공하는 것이 가장 좋습니다.

내가 상실한 OO 설계 트릭이나 패턴이있어 병렬 상속 계층을 모두 피할 수 있습니까?

이 질문은 다소 주관적인 것이지만, 기여한 모든 사람들에게 유용한 점이 있으며 최선의 대답을 답으로 선택하겠습니다.

감사합니다.

편집 :

은 내가 주어진 내 예를 정말 이해가되지 않습니다 실현이 생각을 가졌어요. 물론 Eat에 전달 된 타입은 항상 Animal의 실제 기본 유형 (다형성 호출의 개시자가 알 수없는)에 의존하기 때문에 다형성 방식으로 Animal을 사용할 수 없습니다! 나는 나의 실제 상황을 보여주는 더 좋은 예를 생각할 필요가있다.

+0

디자인에 문제가있는 코드의 예를 들려 줄 수 있습니까? –

+1

왜 제네릭 클래스 대신 제네릭 메서드를 만드는 것이 좋을까요? –

+0

@VictorSorokin 가능한 한 일반적으로 이것을 만들었으므로 추가 코드 예제를 제공 할 수 있는지 잘 모르겠습니다. 제 디자인의 문제점은 제네릭 솔루션이 다형성 (polymorphic) 동작을 허용하지 않고 (꽤 지저분하지 않고) 형식 검사가 컴파일 타임 안전을 제공하지 않는다는 것입니다. – Martyn

답변

3

나는 상식을 생각하고 도메인의 요구 사항에 따라 올바른 접근 방식이 결정됩니다.이 예제 작업,이

public class Lion:Animal 
{ 
     public override void Eat(Food f) 
     { 
      Eat(f as LionFood); 
     } 
     public void Eat(LionFood food) 
     { 
     //check for null food 
     //actually consume it 
     } 
    } 

편집처럼 할 거라고

나는 동물 장난감을 가지고 놀 수있는 경우, 특정 사냥 무엇 때문에이 경우에는 적합하지 않습니다 제네릭을 사용하여 생각 동물 등등. polymorhpism을 사용하는 인수가있는 메소드가있을 때마다 제네릭을 사용하는 것은 어색합니다.

+0

어떤 언어입니까? 그리고 이것은 잔디를 사자에게 먹일 때 컴파일 시간 오류가 발생합니까? – enobayram

+0

그 C#, 아마 너무 자바 (구문에 대한 확실하지)와 함께 작동합니다. Grass가 Food를 상속받지 않으면 컴파일 오류가 발생하지 않습니다. 그렇기 때문에 추상화를 막기 위해 다형성을 사용합니다 (Food는 인터페이스가 될 수 있음). – MikeSW

+0

@MikeSW Mike, 제록스가 제네릭 경로가 왜 지저분해질 수 있는지 설명합니다. 처음에는 좋은 옵션 같지만 대개 잘 나오지 않습니다. 나는 종종 컴파일 타임 검사를 희생하는 대신, 답안 예제에서와 같이 코드에 형식 검사를하는 것이 가장 바람직하다고 생각합니다. – Martyn

1

네가 원하는대로 할 수 있을까? 때때로, 당신이하고 싶은 것을 표현할 수 없을 때, 당신이 원하는 것은 실제로 믹스 인 것입니다.

template<typename base> 
class Eater: public base { 

template<typename T> 
Eat(T (extends Food) food) { // you can do that extends thing in C++ but I won't bother 
    // register what's eaten, or do whatever 
    base::Eat(food); 
} 

} 

class Lion { 
    Eat(LionFood food) { 
     cout<<"Hmm delicious!"; 
    } 
} 

int main() { 
    Eater<Lion> lion; 
    lion.eat(LionFood()); 
    return 0; 
} 

잔디를 사자에게 먹이 려하면 멋진 컴파일러 오류가 발생합니다.