The Visitor Pattern

Friday, November 16, 2007 11:00:13 PM (Mountain Standard Time, UTC-07:00)

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." - wikipedia

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)

#