1

メソッドとクラスの両方に拡張できる二重ディスパッチを実装する方法を探しています。

これまでは、基本的に次の 3 つのアプローチを使用していました。

  • 優れた従来の手続き型アプローチ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();}
}
4

2 に答える 2

0

それは、コードが最も揮発性であることがわかる場所に帰着します。私は、ウィジェットがマークされた各関数で派生する基本クラスを持つルートに行くと思います。そのvirtualため、新しい関数を追加しても、すべての派生クラスが実装を提供する必要はなく、関数を呼び出してもコードは失敗しません。ウィジェット固有の実装を提供していない具象クラス。

于 2013-03-27T09:54:28.027 に答える
0

私は同様の問題に直面しています - 基本的に、ここでの問題は複数のディスパッチの問題であり、単一ディスパッチの OO 言語では十分にサポートされていません。

私が到達した妥協点は、手続き型の例の拡張可能なバリエーションです。

Mediator (または Coordinator) とディクショナリを使用して、2 つのオブジェクト間で発生するアクションを登録および解決します。次のコード例では、2 つのオブジェクト間の衝突の問題を使用しています。

基本的な構造は次のとおりです。

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<TType, TEnum>は自明に実装されています
  • 同様に、型Tuple<T, T>Action<T, T>型を圧縮して、単一の型パラメーターを受け入れることができます。
  • IColliderいくつかの場所でパターンを再利用したい場合は、さらに進んでインターフェースを汎用のものに変更することもできます
  • 拡張可能な列挙型を使用すると、拡張性の別の問題が解決されます (新しい型の追加)。
于 2013-06-18T13:24:49.633 に答える