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

 1     public class BadExam : IExam {
 2         public BadExam( ) : this( new List> IQuestion >( ) ) {}
 3 
 4         public BadExam( IList> IQuestion > questions ) {
 5             _questions = questions;
 6         }
 7 
 8         public int GetCompletedQuestions( ) {
 9             int completedQuestions = 0;
10             foreach ( IQuestion question in _questions ) {
11                 completedQuestions += question.IsComplete( ) ? 1 : 0;
12             }
13             return completedQuestions;
14         }
15 
16         private IList> IQuestion > _questions;
17     }

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

 1     public class Exam : IExam {
 2         public Exam( ) : this( new List> IQuestion >( ) ) {}
 3 
 4         public Exam( IList> IQuestion > questions ) {
 5             _questions = questions;
 6         }
 7 
 8         public void TraverseUsing( IVisitor> IQuestion > visitor ) {
 9             foreach ( IQuestion question in _questions ) {
10                 visitor.Visit( question );
11             }
12         }
13 
14         private IList> IQuestion > _questions;
15     }

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.

 1     public class CompletedQuestionsVisitor : ICompletedQuestionsVisitor {
 2         private int _completedQuestions;
 3 
 4         public void Visit( IQuestion question ) {
 5             _completedQuestions += ( question.IsComplete( ) ? 1 : 0 );
 6         }
 7 
 8         public int TotalCompletedQuestions( ) {
 9             return _completedQuestions;
10         }
11     }

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

 1     public class SpecificationVisitor> T > : ISpecificationVisitor> T > {
 2         private readonly ISpecification> T > _specification;
 3         private int _totalMatching;
 4 
 5         public SpecificationVisitor( ISpecification> T > specification ) {
 6             _specification = specification;
 7         }
 8 
 9         public void Visit( T item ) {
10             _totalMatching += _specification.IsSatisfiedBy( item ) ? 1 : 0;
11         }
12 
13         public int TotalMatching( ) {
14             return _totalMatching;
15         }
16     }

Yes it's a little overboard and slightly contrived, but perhaps someone can find a good home for him.

Source Code (2.28MB)

comments powered by Disqus