2013-03-27 6 views
1

메서드와 클래스 모두에 대해 확장 할 수있는 이중 디스패치를 ​​구현하는 방법을 찾고 있습니다.C#에서 double dispatch 구현 function 및 object가 모두 작동하도록 확장 가능

은 지금까지 나는 기본적으로 세 가지 방법을 사용 :

  • 좋은 switch (새 클래스를 추가하기 어려운 새로운 기능을 쉽게 추가 할 수)
  • 방문자 패턴 (매우 유사과 전통적인 절차 적 접근 : 쉽게
  • ) 나는 보라 해요

) 새로운 기능을 추가 (새로운 클래스를 쉽게 추가 할 하드 간단한 인터페이스 방식을 새로운 클래스를 추가, 하드 새로운 방문자를 추가 함수 나 기존 클래스를 수정하지 않고도 새 함수와 새 클래스를 모두 추가 할 수있는 방법을 제공합니다.

적어도 프로그램 시작 후 한 번 수표를 찍은 후에는 개체/기능의 특정 조합을 요청할 때 실패하지 않아야합니다. 여기

내가 지금까지 사용되는 방법은 다음과 같습니다

전통적인 절차 적 접근 :

enum WidgetType {A,B,C,} 

interface IWidget 
{ 
    WidgetType GetWidgetType(); 
} 

class WidgetA 
{ 
    public WidgetType GetWidgetType() {return WidgetType.A;} 
} 
class WidgetB 
{ 
    public WidgetType GetWidgetType() {return WidgetType.B;} 
} 
class WidgetC 
{ 
    public WidgetType GetWidgetType() {return WidgetType.C;} 
} 
// new classes have to reuse existing "WidgetType"s 
class WidgetC2 
{ 
    public WidgetType GetWidgetType() {return WidgetType.C;} 
} 


class Functions 
{ 
    void func1(IWidget widget) 
    { 
     switch (widget.GetWidgetType()) 
     { 
      case WidgetType.A: 
       ... 
       break; 
      case WidgetType.A: 
       ... 
       break; 
      case WidgetType.A: 
       ... 
       break; 
      default: 
       // hard to add new WidgetTypes (each function has to be augmented) 
       throw new NotImplementedException(); 
     } 
    } 

    // other functions may be added easily 
} 

전통적인 객체 지향 접근 방식 (방문자-패턴) :

interface IWidgetVisitor 
{ 
    void visit(WidgetA widget); 
    void visit(WidgetB widget); 
    void visit(WidgetC widget); 
    // new widgets can be easily added here 
    // but all visitors have to be adjusted 
} 

interface IVisitedWidget 
{ 
    void accept(IWidgetVisitor widgetVisitor); 
} 

class WidgetA : IVisitedWidget 
{ 
    public void accept(IWidgetVisitor widgetVisitor){widgetVisitor.visit(this);} 
    public void doStuffWithWidgetA(){} 
} 
class WidgetB : IVisitedWidget 
{ 
    public void accept(IWidgetVisitor widgetVisitor){widgetVisitor.visit(this);} 
    public void doStuffWithWidgetB(){} 
} 
class WidgetC : IVisitedWidget 
{ 
    public void accept(IWidgetVisitor widgetVisitor){widgetVisitor.visit(this);} 
    public void doStuffWithWidgetB(){} 
} 

class SampleWidgetVisitor : IWidgetVisitor 
{ 
    public void visit(WidgetA widget){ widget.doStuffWithWidgetA(); } 
    public void visit(WidgetB widget){ widget.doStuffWithWidgetB(); } 
    public void visit(WidgetC widget){ widget.doStuffWithWidgetC(); } 
} 

간단한 인터페이스 방식 :

IWidget 
{ 
    void DoThis(); 
    void DoThat(); 
    // if we want to add 
    // void DoOtherStuff(); 
    // we have to change each class 
} 

WidgetA : IWidget 
{ 
    public void DoThis(){ doThisForWidgetA();} 
    public void DoThat(){ doThatForWidgetA();} 
} 
WidgetB : IWidget 
{ 
    public void DoThis(){ doThisForWidgetB();} 
    public void DoThat(){ doThatForWidgetB();} 
} 
WidgetC : IWidget 
{ 
    public void DoThis(){ doThisForWidgetC();} 
    public void DoThat(){ doThatForWidgetC();} 
} 

답변

0

코드가 가장 휘발성이 높은 곳으로 내려갑니다. 위젯이 virtual이라고 표시된 각각의 함수로부터 파생 된 기본 클래스를 가지기를 바랄 것이므로 새로운 함수를 추가 할 때 모든 파생 클래스가 구현을 제공 할 필요가 없으며 함수를 호출하면 코드가 실패하지 않습니다. 위젯 관련 구현을 제공하지 않은 구체적인 클래스에서

0

저는 비슷한 문제에 직면하고 있습니다. 근본적으로 단일 발송 OO 언어에서 잘 지원되지 않는 다중 발송의 문제입니다.

내가 타협 한 점은 절차상의 예에 대한 확장 가능한 변형입니다.

사전에 중재자 (또는 코디네이터)를 사용하여 두 개체간에 발생해야하는 작업을 등록하고 해결합니다. 다음 코드 예제에서는 두 개체 간의 충돌 문제를 사용하고 있습니다.

기본 구조는 다음과 같이

enum CollisionGroup { Bullet, Tree, Player } 

interface ICollider 
{ 
    CollisionGroup Group { get; } 
} 

중개자 개체가 정의된다

class CollisionResolver 
{ 
    Dictionary<Tuple<CollisionGroup, CollisionGroup>, Action<ICollider, ICollider>> lookup 
     = new Dictionary<Tuple<CollisionGroup, CollisionGroup>, Action<ICollider, ICollider>>(); 

    public void Register(CollisionGroup a, CollisionGroup b, Action<ICollider, ICollider> action) 
    { 
     lookup[Tuple.Create(a, b)] = action; 
    } 

    public void Resolve(ICollider a, ICollider b) 
    { 
     Action<ICollider, ICollider> action; 
     if (!lookup.TryGetValue(Tuple.Create(a.Group, b.Group), out action)) 
      action = (c1, c2) => Console.WriteLine("Nothing happened..!"); 

     action(a, b); 
    } 
} 

깔깔! 그것은 멋져 보이지 않지만 그것은 주로 일반적인 유형과 지원되는 객체의 부족 때문입니다. 나는이 답안의 범위에 너무 많은 복잡성을 초래할 것이기 때문에이 예제를 위해 아무 것도 만들지 않았다.

개체

은과 같이 사용됩니다

var mediator = new CollisionResolver(); 
mediator.Register(CollisionGroup.Bullet, CollisionGroup.Player, 
    (b, p) => Console.WriteLine("A bullet hit {0} and it did not end well", p)); 

mediator.Register(CollisionGroup.Player, CollisionGroup.Tree, 
    (p, t) => Console.WriteLine("{0} ran into a tree. Ouch", p)); 

mediator.Register(CollisionGroup.Player, CollisionGroup.Player, 
    (p1, p2) => Console.WriteLine("{0} and {1} hi-fived! Yeah! Awesome!", p1, p2)); 

var jeffrey = new Player("Jeffrey"); 
var cuthbert = new Player("Cuthbert"); 
var bullet = new Bullet(); 
var tree = new Tree(); 

mediator.Resolve(jeffrey, cuthbert); // Jeffrey and Cuthbert hi-fived! Yeah! Awesome! 
mediator.Resolve(jeffrey, tree);  // Jeffrey ran into a tree. Ouch 
mediator.Resolve(bullet, cuthbert); // A bullet hit Cuthbert and it did not end well 
mediator.Resolve(bullet, tree);  // Nothing happened..! 

이 방법은 내가 찾을 수있는 가장 확장이다. 새로운 반응이나 새로운 유형을 추가하려면 새로운 열거 형 멤버와 .Register() 메서드 호출이 필요합니다.

는 일반적인 DispatchMediator<TType, TEnum>이 하찮게 구현
  • 은 마찬가지로, Tuple<T, T>Action<T, T> 유형은 단일 유형의 매개 변수를 받아 응축 될 수있다하는 것은
  • 당신도 갈 수 : 위의 방법에 확장을위한

    포인트 여러 장소

  • 의 사용에 패턴을 다시 사용하려는 경우 추가 및 일반적인 하나에 ICollider 인터페이스 변경 10은 다른 유형의 확장 성 문제를 해결합니다 (새로운 유형 추가)
+0

그러나 어떻게 "칼"과 같은 새로운 유형의 CollisionGroup을 추가 할 수 있습니까? enum은 확장 할 수 없습니다. 라이브러리에 코드의 일부를 배치하고 싶습니다. (나중에 코드를 변경하는 것을 싫어합니다.) – Onur

+0

대답의 마지막 줄을 확인하십시오 - 확장 가능한 enum을 만들 수 있습니다 (enum은 실제로 int 또는 다른 원시 값 유형이라는 것을 기억하십시오) – AlexFoxGill

+0

가능한 모든 조합이 availble인지, 즉 가질 수 있는지 미리 확인하고 싶습니다. 디폴트의 ​​「오퍼레이션 없음」조작, 또는 사용시에 예외를 Throw합니다. 어떻게 그걸 처리 하겠니? – Onur