在c#可扩展中实现双重分派,以便对函数和对象进行操作

本文关键字:函数 对象 操作 可扩展 实现 分派 | 更新日期: 2023-09-27 18:06:00

我正在寻找一种实现双重分派的方法,可以扩展到方法和类。

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

  • 传统的过程化方法,具有很好的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();}
}

在c#可扩展中实现双重分派,以便对函数和对象进行操作

这实际上取决于您看到的代码最不稳定的地方。我想我会走的路线有一个基类的窗口小部件从每个函数标记virtual派生,所以添加一个新函数不需要所有的派生类提供一个实现,你的代码不会失败,如果你调用一个具体的类,没有提供窗口小部件特定的实现函数。

我正面临着一个类似的问题——本质上,这里的问题是多分派,单分派OO语言不支持多分派。

我的折衷方案是在你的procedure示例的基础上扩展一个变体。

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

基本结构如下:

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..!

这个方法是我能找到的最具扩展性的方法。要添加新反应或新类型,只需要添加一个新的enum成员并调用.Register()方法。

以上方法的展开要点:

  • 通用DispatchMediator<TType, TEnum>的简单实现
  • 类似地,Tuple<T, T>Action<T, T>类型可以被压缩为接受单个类型参数
  • 如果你想在几个地方重用该模式,你甚至可以进一步将ICollider接口更改为通用接口
  • 可扩展枚举的使用解决了可扩展性的另一个问题(添加一个新类型)