【问题标题】:Implementing a double dispatch in C# extensible for both functions and objects to operate on在 C# 中实现双重分派,可扩展函数和对象以对其进行操作
【发布时间】:2013-03-27 09:46:46
【问题描述】:

我正在寻找一种方法来实现可以为方法和类扩展的双重调度。

到目前为止,我基本上使用了三种方法:

  • 具有出色 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
}

传统的面向对象方法(Visitor-Pattern):

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();}
}

【问题讨论】:

    标签: c# design-patterns visitor-pattern double-dispatch


    【解决方案1】:

    这真的归结为您看到代码最不稳定的地方。我想我会选择一个基类,每个函数都标记为 virtual未提供 Widget 特定实现的具体类上的函数。

    【讨论】:

      【解决方案2】:

      我面临着一个类似的问题 - 这里的问题本质上是多调度问题,这在单调度 OO 语言中没有得到很好的支持。

      我达成的妥协是您的程序示例的可扩展变体。

      它使用带有字典的调解器(或协调器)来注册和解决两个对象之间应该发生的动作。在下面的代码示例中,我使用的是两个对象之间的碰撞问题。

      基本结构是:

      enum CollisionGroup { Bullet, Tree, Player }
      
      interface ICollider
      {
          CollisionGroup Group { get; }
      }
      

      Mediator 对象定义如下:

      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&lt;TType, TEnum&gt; 被简单地实现了
      • 同样,Tuple&lt;T, T&gt;Action&lt;T, T&gt; 类型可以压缩为接受单个类型参数
      • 如果您想在多个地方重用该模式,您甚至可以更进一步,将 ICollider 接口更改为通用接口
      • extensible enums 的使用解决了另一个可扩展性问题(添加新类型)

      【讨论】:

      • 但是如何添加一个新类型的CollisionGroup ,比如“剑”?枚举是不可扩展的吗?我想将至少部分代码放在库中(以后不喜欢更改)。
      • 检查答案的最后一行 - 您可以创建一个可扩展的枚举(请记住,枚举实际上只是一个 int 或其他原始值类型)
      • 我想提前检查每个可能的组合是否可用,即没有默认的“no-op”操作或在使用时抛出异常。你会怎么处理呢?
      • 该类必须将可能的状态列表作为构造函数参数,然后在所有内容“连接好”后执行检查。需要明确说明(或者您可以使用反射来抓取特定基类的子类型的所有程序集)
      猜你喜欢
      • 2011-09-14
      • 1970-01-01
      • 1970-01-01
      • 2017-11-28
      • 2016-08-26
      • 2013-03-27
      • 2023-01-04
      • 2012-10-28
      • 2017-11-29
      相关资源
      最近更新 更多