Recently I was reading Judith Bishop's
C# 3.0 Design Patterns. The book has been a good read so far.The
Visitor pattern implementation in the book however is implemented "classically" wherein each element of the structure being iterated over has an
Accept operation which accepts an instance of a
Visitor type. When the client invokes the operation on the
Visitor which is supposed to kick start the visiting,the
Visitor iterates over each element of the structure in some way and invokes the
Accept operation passing in itself as a reference. The element in turn invokes the
Visit operation on the
Visitor (passing in itself) which in turn accepts an element instance and works with it(double dispatch). But my gut tells me something seems out of place here given that the book's intent is to implement the classic GOF patterns using C# 3.0 features. To put things into perspective, I created a small generic linked list implementation based on the classic
Visitor pattern
I see a number of drawbacks with this approach:
- No creative use of C# 3.0 features
- The Node<T> needs to know about the IVisitor interface(interface coupling).
- If the LinkedList<T> was written by a 3rd party vendor, there would be no way to "pass in" a Visitor implementation
- Double dispatch can hurt performance
The following implementation uses C# 3.0 features
- The structure being iterated over has no knowledge of a Visitor
- The iteration logic is encapsulated away into the ForEach extension method which can be reused for iterating over any IEnumerable<T>
- The ForEach extension method accepts an Action<T> which represents the Visitor.
- The client is free to pass in any lambda representing the visiting logic; it could very well request an Action<T>from some factory(not shown).
- No double dispatch
- No proliferation of Visitor class hierarchies.Less code, more readabiliy and ease of maintenance
I prefer this mechanism much more over the interface based approach.With the functional programming features that C# provides, I need a compelling reason to use interfaces for tasks such as this.