user35443
user35443

Reputation: 6403

Visitor pattern - interface vs abstract class

I'm currently trying to write a simple compiler for even simpler language, but I have problem with adding the Visitor pattern. I have a ILanguageVisitor interface that looks like this:

interface ILanguageVisitor {
        void Visit(GlobalStructure cs);
        void Visit(GotoStructure cs);
        void Visit(IfStructure cs);
        void Visit(ElseIfStructure cs);
        void Visit(ElseStructure cs);
        ...
}

All of these methods must be implement to create concrete visitor for specific architecture. But above this, there are some methods and fields that should be common for all possible visitors.

That is for example functionality of blocks, which consist of two or more Visit calls, like:

Visit(If) 
    ...
Visit(Else) 
    ... 
Visit(EndIf) 

Visit(For) 
    ... 
Visit(EndFor)

This is because of the rules of block start and termination (like there can't be two elses in one block, or that child block can't be terminated by parent as in case For ... If ... EndFor).

My question is: if I have a behavior that should be common for all Visitors, should I create an abstract Visitor class, which would make these specific methods virtual and other abstract?

Will be there any loss of Visitor´s point if I add a default behavior to its base?

Upvotes: 3

Views: 1411

Answers (3)

GSerjo
GSerjo

Reputation: 4778

As for me the visitor pattern is a smart If-Else construction, take a look on this solution

public static class Visitor
{
    public static IFuncVisitor<TBase, TResult> For<TBase, TResult>()
        where TBase : class
    {
        return new FuncVisitor<TBase, TResult>();
    }

    public static IActionVisitor<TBase> For<TBase>()
        where TBase : class
    {
        return new ActionVisitor<TBase>();
    }

    private sealed class ActionVisitor<TBase> : IActionVisitor<TBase>
        where TBase : class
    {
        private readonly Dictionary<Type, Action<TBase>> _repository =
            new Dictionary<Type, Action<TBase>>();

        public void Register<T>(Action<T> action)
            where T : TBase
        {
            _repository[typeof(T)] = x => action((T)x);
        }


        public void Visit<T>(T value)
            where T : TBase
        {
            Action<TBase> action = _repository[value.GetType()];
            action(value);
        }
    }

    private sealed class FuncVisitor<TBase, TResult> : IFuncVisitor<TBase, TResult>
        where TBase : class
    {
        private readonly Dictionary<Type, Func<TBase, TResult>> _repository =
            new Dictionary<Type, Func<TBase, TResult>>();

        public void Register<T>(Func<T, TResult> action)
            where T : TBase
        {
            _repository[typeof(T)] = x => action((T)x);
        }

        public TResult Visit<T>(T value)
            where T : TBase
        {
            Func<TBase, TResult> action = _repository[value.GetType()];
            return action(value);
        }
    }
}

public interface IFuncVisitor<in TBase, TResult>
    where TBase : class
{
    void Register<T>(Func<T, TResult> action)
        where T : TBase;

    TResult Visit<T>(T value)
        where T : TBase;
}

public interface IActionVisitor<in TBase>
    where TBase : class
{
    void Register<T>(Action<T> action)
        where T : TBase;

    void Visit<T>(T value)
        where T : TBase;
}

Usage example:

IActionVisitor<Letter> visitor = Visitor.For<Letter>();
visitor.Register<A>(x => Console.WriteLine(x.GetType().Name));
visitor.Register<B>(x => Console.WriteLine(x.GetType().Name));

Letter a = new A();
Letter b = new B();
visitor.Visit(a);
visitor.Visit(b);

Where Letter is the base class for A and B

Upvotes: 2

Doc Brown
Doc Brown

Reputation: 20044

Design patterns are solutions for design problems, not implementation rules. So if you choose to implement a visitor pattern using interfaces, abstract classes, or both (like @AdiLester suggested) is completly up to you

If I remember correctly, the original GOF book used a lot of C++ exmples, where there is not even a language element interface.

Upvotes: 1

Adi Lester
Adi Lester

Reputation: 25201

In these kind of cases, I believe the best solution is to have both an interface and a base class which implements this interface and allows users to override certain methods. This way, the user can decide whether he'd like to:

  • Inherit the base class and have some or most functionality already implemented for him (with the possible cost of flexibility)
  • Implement the entire interface himself, allowing for maximum control and flexibility.

Upvotes: 2

Related Questions