17 November 2007

The Visitor Pattern

by mo


One of the many cool things we learned last week was on how to traverse a collection using a visitor.

“…the visitor design pattern is a way of separating an algorithm from an object structure. A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures.”

Let’s pretend that I’ve got an exam, and this exam has a bunch of questions and I want to find out how many questions have been completed. I could throw a method on my IExam type that returns the number of completed questions. Kind of like…

public class BadExam : IExam 
{
    public BadExam( ) : this( new List> IQuestion >( ) ) {}

    public BadExam( IList> IQuestion > questions ) 
    {
        _questions = questions;
    }

    public int GetCompletedQuestions( ) 
    {
        int completedQuestions = 0;
        foreach ( IQuestion question in _questions ) 
        {
            completedQuestions += question.IsComplete( ) ? 1 : 0;
        }
        return completedQuestions;
    }

    private IList> IQuestion > _questions;
}

But what happens when I want to find out other statistics about the questions in my exam. I could add additional methods to the IExam type for each type of information that I want to query on, but this would totally violate the Open/Closed Principle. Let’s try to solve the same problem above using a visitor…

public class Exam : IExam 
{
    public Exam( ) : this( new List> IQuestion >( ) ) {}

    public Exam( IList> IQuestion > questions ) 
    {
        _questions = questions;
    }

    public void TraverseUsing( IVisitor> IQuestion > visitor ) 
    {
        foreach ( IQuestion question in _questions ) 
        {
            visitor.Visit( question );
        }
    }

    private IList> IQuestion > _questions;
}

What this allows me to do is create new implementations of IVisitor’s that traverse the collection of questions but collect information that it needs. For example we could have a “CompletedQuestionsVisitor” that keeps track of the number of completed questions.

public class CompletedQuestionsVisitor : ICompletedQuestionsVisitor 
{
    private int _completedQuestions;

    public void Visit( IQuestion question ) 
    {
        _completedQuestions += ( question.IsComplete( ) ? 1 : 0 );
    }

    public int TotalCompletedQuestions( ) 
    {
        return _completedQuestions;
    }
}

Now I can traverse the internal collection of questions and pick out the information that I need. What if I wanted to build a tree of specifications and wanted to traverse a collection of items and keep track of only those items that match my composite specification. Introducing the “Specification Visitor”.

public class SpecificationVisitor> T > : ISpecificationVisitor> T > 
{
    private readonly ISpecification> T > _specification;
    private int _totalMatching;

    public SpecificationVisitor( ISpecification> T > specification ) 
    {
        _specification = specification;
    }

    public void Visit( T item ) 
    {
        _totalMatching += _specification.IsSatisfiedBy( item ) ? 1 : 0;
    }

    public int TotalMatching( ) 
    {
        return _totalMatching;
    }
}

Yes it’s a little overboard and slightly contrived, but perhaps someone can find a good home for him. Source Code (2.28MB)

csharp designpatterns