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)