The Visitor Pattern Posted on November 17, 2007 @ 06:00 csharp, designpatterns
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.