views:

288

answers:

3

Hi there, i have a question regarding the visitor pattern, I currently have two assemblies. My first assembly contains several interfaces.

public interface INode
{
    void Visit(INodeVisitor visitor);
}

public interface INodeVisitor
{
    void VisitContainer(IContainer container);
}

public interface IContainer : INode
{
}

And my second assembly

    class Program
{
    static void Main(string[] args)
    {
        ContainerVisitor visitor = new ContainerVisitor();
        visitor.VisitContainer(new Container());
    }
}

public class ContainerVisitor : INodeVisitor
{
    public void VisitContainer(IContainer value)
    {
        Container container = value as Container;
        // Do some stuff...
    }
}

public class Container : IContainer
{        
    public void Visit(INodeVisitor visitor)
    {
        visitor.VisitContainer(this);
    }
}

What i want to do is avoid the need to cast in the ContainerVisitor class, I want to reference the Container directly. I cannot change the interface INodeVisitor interface to use Container. Any ideas? Should i just cast?

Cheers

Rohan

+3  A: 

The cast is unavoidable, but you could abstract it out a bit to remove it from the actual ContainerVisitor class.

public class NodeVisitor<T> : INodeVisitor where T : IContainer {
  public void VisitContainer(T node) {
    var container = node as T;
    if ( container != null ) { 
      VisitTyped(container);
    }
  }
  protected abstract VisitContainerTyped(T container);
}

Now ContainerVisitor can derive from NodeVisitor and avoid the cast

public class ContainerVisitor : NodeVisitor<Container>{
    protected override void VisitContainerTyped(Container container){
        // Do some stuff...
    }
}
JaredPar
+1  A: 

I don't think it is valid to assume that it is a Container instance. I could, perfectly validly, write my own IContainer implementation, and your implementation would choke on it, breaking the whole point of interface-based abstraction. You also can't (validly) just change the API to accept Container (using explicit implementation to support IContainer), since I could be using the interface rather than the class:

INodeVisitor visitor = new ContainerVisitor();
visitor.VisitContainer(myBespokeContainer);

If you want optional support for something from the base-class, then you are going to have to use "as" to detect it. Anything else and you are breaking the abstraction.

Marc Gravell
+1  A: 

Unless you can define your IContainer interface better, then you won't be able to avoid the cast. And as Marc says, that defeats the purpose of the interface-based abstraction.

public interface IContainer : INode
{
    void Add(IComponent component);
    void Add(IComponent component, string name);
    void Remove(IComponent component);
    ComponentCollection Components { get; }
}

With the better defined IContainer, your Visitors won't need to do any casting.

public class ContainerVisitor : INodeVisitor
{
    public void VisitContainer(IContainer container)
    {
        foreach (IComponent component in container.Components)
        {
            // ...
        }
    }
}
Mufaka