2009-03-22 8 views
3

기본 클래스에서 모두 상속받은 객체의 컬렉션이 있다고 가정 해 보겠습니다. 뭔가 같은 ...다운 캐스트를 방지하기 위해이 디자인을 변경하는 방법은 무엇입니까?

abstract public class Animal 
    { 

    } 

    public class Dog :Animal 
    { 

    } 

    class Monkey : Animal 
    { 

    } 

지금, 우리는이 동물에게 먹이를해야하지만 그들은 자신을 공급하는 방법을 알 수 없습니다. 그들은 자신을 공급하는 방법을 알 수 없다, 그러나

foreach(Animal a in myAnimals) 
{ 
    a.feed(); 
} 

, 그래서 우리는 다음과 같이 수행 할 : 할 수있을 경우, 대답은 간단 것 물론

class Program 
{ 
    static void Main(string[] args) 
    { 
     List<Animal> myAnimals = new List<Animal>(); 

     myAnimals.Add(new Monkey()); 
     myAnimals.Add(new Dog()); 

     foreach (Animal a in myAnimals) 
     { 
      Program.FeedAnimal(a); 
     } 
    } 

    void FeedAnimal(Monkey m) { 
     Console.WriteLine("Fed a monkey."); 
    } 

    void FeedAnimal(Dog d) 
    { 
     Console.WriteLine("Fed a dog."); 
    } 

} 

이 원 다운 캐스트를 강요하는 것처럼 컴파일하지 마라.

디자인 패턴이나이 문제를 해결하는 데 도움이되는 제네릭을 포함한 다른 솔루션이있는 것처럼 느껴지지만 아직 내 손가락을 넣지 않았습니다.

제안 사항?

답변

7

Linq 관용구에서 사용되는 확인 된 다운 캐스트는 완벽하게 안전합니다.

foreach (Monkey m in myAnimals.OfType<Monkey>()) 
    Program.FeedAnimal(m); 

방문자 패턴을 사용할 수도 있습니다. visitor 객체는 모든 유형의 동물을 알고 있으므로 각 객체에 대해 FeedAnimal 함수가 있습니다. 방문자 개체를 동물의 Feed 함수에 전달하고 올바른 FeedAnimal 메서드를 호출하여 this을 전달합니다. 당신이 시작하기 위해이 작업을 수행 할 것,

private static Dictionary<Type, Action<Animal>> _feeders; 

가 공급 조치를 등록하려면 :

이 확장하기 위해, 당신은 사료 공급기의 Dictionary 필요

_feeders[typeof(Monkey)] = 
    a => 
    { 
     Monkey m = (Monkey)a; 

     // give food to m somehow 
    }; 

하지만 내리 뜬있다 , 당신은 올바른 타입의 키를 제공해야합니다. 완전히 안전하게 재사용 할 수 있도록이 패턴을 캡처

public static void AddFeeder<TAnimal>(Action<TAnimal> feeder) where TAnimal : Animal 
{ 
    _feeders[typeof(TAnimal)] = a => feeder((TAnimal)a); 
} 

:

AddFeeder<Monkey>(monkey => GiveBananasTo(monkey)); 
AddFeeder<Dog>(dog => ThrowBiscuitsAt(dog)); 

당신이 동물에게 먹이를해야 할 때 그리고,이 확장 방법을 사용

public static void Feed(this Animal a) 
{ 
    _feeders[a.GetType()](a); 
} 
을 그래서 도우미를 만들기

예 :

a.Feed(); 

그래서 _feeders는 AddFeeder 방법과 함께 확장 메소드와 같은 정적 클래스의 개인 정적 필드 것입니다.

업데이트 : 또한 상속을 지원 한 곳에서 모든 코드 : 당신은 또한 각 유형 t하여 인터페이스 지원을 통해 루프를 가질 수

public static class AnimalFeeding 
{ 
    private static Dictionary<Type, Action<Animal>> _feeders 
     = new Dictionary<Type, Action<Animal>>(); 

    public static void AddFeeder<TAnimal>(Action<TAnimal> feeder) 
     where TAnimal : Animal 
    { 
     _feeders[typeof(TAnimal)] = a => feeder((TAnimal)a); 
    } 

    public static void Feed(this Animal a) 
    { 
     for (Type t = a.GetType(); t != null; t = t.BaseType) 
     { 
      Action<Animal> feeder; 
      if (_feeders.TryGetValue(t, out feeder)) 
      { 
       feeder(a); 
       return; 
      } 
     } 

     throw new SystemException("No feeder found for " + a.GetType()); 
    } 
} 

- 그래서 '기본적으로 같은 생각 여기 예제를 간단하게 유지했습니다.

+0

(이후 요청 됨)'OfType'은 괜찮지 만, 당신이 품종을 소유 한만큼 많은 OfType을 가져야 만합니다. 결국 방문이됩니다. 여기에 설명 된 사전/위임 기반 방문자 구현은 많은 유효한 접근 방법 중 하나이지만 상속에 대해서는 조금 생각하면됩니다. –

+0

... 즉 Dog에 대한 액션을 등록했지만 Collie 인스턴스를 추가하면 중단됩니다. 또한 람다는 AddFeeder (GiveBananasTo)에 단순화 될 수 있습니다. AddFeeder (ThrowBiscuitsAt). 하지만 합리적인 토론 - = p –

+0

OfType 내가 응답을 나머지 부분에 대한 소개로 안전하게 캡슐화하는 한 다운 캐스트가 얼마나 괜찮은지 보여주기 위해 실제로 언급했습니다. –

0

동물은 자신이나 다른 동물을 먹일 수 없으므로 동물을 먹일 사적인 방법을 소유하고있는 "소유자"또는 "키퍼"또는 "관리인"클래스를 만드는 다른 방법은 없습니다.

1

이것은 OOD의 전형적인 문제입니다. 클래스 (동물) 세트가 고정되어있는 경우 visitor pattern을 사용할 수 있습니다. 액션 세트 (예 : 피드)가 제한적인 경우 Animal에 피드()를 추가하기 만하면됩니다. 이 중 어느 것도 보유하지 않으면 쉬운 해결책이 없습니다.

1

제네릭은 "개 목록"이 있고 "동물의 목록"(즉 List<T> where T : Animal) 인 인수가있는 메소드를 호출하려는 경우에만 유용합니다 - 여기서는 도움이되지 않는다고 생각합니다. .

나는 당신이 ...

visitor pattern에게 ... 동물의 특정 유형을 공급하는 방법을 알고, 당신은 방법을 알고 하나를 찾을 때까지 그들을 계속 시도 할 수 개체의 일부 세트를 필요로하기 위하여려고하고있다 의심
+0

내 대답에 대한 귀하의 의견을 듣고 싶습니다, 마크. –

0

동물 클래스에 동물의 유형을 식별 할 수있는 멤버 변수를 포함시킨 다음 피드 함수에서이 변수를 읽고이를 기반으로 다른 결과를 생성 할 수 있습니다.

2

먼저 객체 지향 디자인의 주요 요점 중 하나는 객체가 데이터에 작용하는 행동 (즉, "동물은 자신을 먹이는 법을 알고 있습니다")과 데이터를 묶는 것입니다. 그래서 이것은 이것들 중 하나입니다 "의사,이 일을 할 때 상처를 입습니다 - 그렇게하지 마십시오"상황.

그렇다면 내가 말한 것보다 스토리에 더 많은 것이 있으며 적절한 "OOD"를 수행 할 수 없다는 좋은 이유가 있다고 확신합니다. 그래서 몇 가지 옵션이 있습니다.

FeedAnimal (Animal a) 방법으로 리플렉션을 사용하여 동물의 유형을 찾을 수 있습니다. 기본적으로 FeedAnimal 메서드에서 다형성을 수행하고 있습니다.

static void FeedAnimal(Animal a) 
{ 
    if (a is Dog) 
    { 
     Console.WriteLine("Fed a dog."); 
    } 
    else if (a is Monkey) 
    { 
     Console.WriteLine("Fed a monkey."); 
    } 
    else 
    { 
     Console.WriteLine("I don't know how to feed a " + a.GetType().Name + "."); 
    }  
} 

더 객체 지향하지만, 그 일의 더 복잡한 방법은 다른 사람에 의해 제안 된 방문자 패턴을 사용하는 것입니다. 이것은 숙련 된 개발자에게는 좀 더 우아하지만 초보 프로그래머에게는 더 명확하고 읽기 어렵지 않을 것입니다. 어떤 접근 방식을 선호하는지는 얼마나 많은 Animal 유형을 가지고 있는지에 달려 있습니다.

+0

당신은 'is'를 사용할 수 있습니다 : a는 Dog, a는 원숭이입니다 ... – strager

+0

아, 좋은 지적입니다. 방문자 패턴은 –

+0

+1입니다. –