1

I'm looking for a way to implement a double dispatch that can be extended for both methods and classes.

Until now I used basically three approaches:

  • the traditional procedural approach with a great switch (easy to add new functions, hard to add new classes)
  • the visitor pattern (quite similar: easy to add new visitors, hard to add new classes)
  • a simple interface approach (easy to add new classes, hard to add new functions)

I'm looking for a way to be able to add both new functions and new classes without having to modify the functions or existing classes.

This should not fail upon requesting a certain combination of object / function, at least not after a check I could do once after program startup.

Here are the approaches I used so far:

Traditional procedural approach:

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
}

Traditional object-oriented approach (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(); }
}

simple interface approach:

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();}
}
Onur
  • 5,017
  • 5
  • 38
  • 54

2 Answers2

0

It really comes down to where you see the code being most volatile. I suppose I would go the route of having a base class that the Widgets derive from with each function marked virtual so adding a new function doesn't require that all deriving classes provide an implementation and your code won't fail if you call the function on a concrete class that hasn't provided a Widget specific implementation.

Rob Epstein
  • 1,450
  • 9
  • 11
0

I'm facing a similar problem - essentially the issue here is that of multiple dispatch, which is not well-supported in single-dispatch OO languages.

The compromise I have come to is an extensible variation on your Procedural example.

It uses a Mediator (or Coordinator) with a dictionary to register and resolve the actions that should occur between two objects. In the following code example, I am using the problem of collisions between two objects.

The basic structures are:

enum CollisionGroup { Bullet, Tree, Player }

interface ICollider
{
    CollisionGroup Group { get; }
}

The Mediator object is defined as follows:

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

Yuck! It doesn't look that nice but that is mainly due to the generic types and lack of supporting objects. I haven't made any for this example because that would introduce too much complexity for the scope of this answer.

The object is used like so:

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

This approach is the most extensible I can find. To add a new reaction or a new type, all that is needed is a new enum member and a call to the .Register() method.

Points for expansion on the above approach:

  • A generic DispatchMediator<TType, TEnum> is trivially implemented
  • Similarly, the Tuple<T, T> and Action<T, T> types can be condensed to accept a single type parameter
  • You could even go further and change the ICollider interface to a generic one if you want to reuse the pattern in several places
  • The use of extensible enums solves another issue of extensibility (adding a new type)
Alex
  • 7,639
  • 3
  • 45
  • 58
  • But how can you add a new type of `CollisionGroup ` like "sword"? An enum is imho not extensible? I'd like to place at least part of the code in a library (and dislike to change it later on). – Onur Jun 21 '13 at 09:26
  • Check the final line of the answer - you can create an extensible enum (remember, an enum is really just an int or other primitive value type) – Alex Jun 21 '13 at 10:57
  • I'd like to check in advance if every possible combination is availble, i.e. not have a default "no-op" operation or throw an exception when used. How would you deal with that? – Onur Jun 24 '13 at 11:35
  • The class would have to take a list of possible states as a constructor parameter, then perform the check after everything is 'wired up'. It would need to be explicitly stated (or you could use reflection to scrape all assemblies for subtypes of a specific base class) – Alex Jun 24 '13 at 12:39