This is my
first time posting a new discussion on the forum. So please bear with me for
any mistakes. Today I am going to discuss a useful but rarely used( maybe
because of its difficulties :-P) pattern, visitor pattern.
According
to dofactory.com definition of the pattern,
“Visitor
pattern Represent an operation to be performed on the elements of an
object structure. Visitor lets you define a new operation without changing the
classes of the elements on which it operates.”
In a
nutshell, the pattern allows you to add, remove or modify an operation without
changing the structure of the classes that it operates on, that is the Element
class. The purpose of the element class is only to accept visitors. Inside the
implementation in the Accept(), it will call visitor’s class visit() method.
This is where the real implementation lies. Therefore as you can see, there is
no change in the Element class(es) if there is a change of implementation to
the visitors’ classes. Below is the structure of the Visitor pattern:
using System;
using
System.Collections.Generic;
namespace
Patterns.GOF.Visitor
{
//test pattern
static void Main(string[] args)
{
StudentCollection students = new StudentCollection();
students.AddStudent(new Bachelor("Harry",3.0));
students.AddStudent(new Bachelor("Mary",3.5));
students.AddStudent(new Masters("Barry",2.1));
students.AddStudent(new Masters("Tom",2.8));
students.AddStudent(new Bachelor("Terry",3.4));
students.AddStudent(new Masters("Jen",3.7));
students.AddStudent(new Bachelor("Roy",3.1));
Course c1 = new HistoryCourse();
Course c2 = new GeographyCourse();
students.Accept(c1);
Console.WriteLine();
students.Accept(c2);
Console.WriteLine();
c1.Count();
Console.WriteLine();
c2.Count();
Console.ReadLine();
}
//Element
public interface CollegeTitle
{
void
Accept(ICourse course);
}
//Visitor
public interface ICourse
{
void
Visit(CollegeTitle people);
}
public abstract class Course : ICourse
{
protected
int MCount { get;
set; }
protected
int BCount { get;
set; }
public virtual void Visit(CollegeTitle people)
{
if
(people is Bachelor)
{
BCount++;
}
if
(people is Masters)
{
MCount++;
}
}
protected
abstract bool
CheckQualified(Student people);
public abstract void
Count();
}
//Visitor
Concrete method
public class HistoryCourse
: Course
{
//Implementation of the visit method. The whole execution
of the pattern lies
//in this
method. Changing the implementation of this method will not affect
// the
element classes'.
public override void Visit(CollegeTitle visitable)
{
Student
student = visitable as Student;
base.Visit(visitable);
if(CheckQualified(student))
Console.WriteLine("Student {0} from {1} has been qualified to enroll
for history course"
,
student.Name
,
student.CourseName);
}
public override void Count()
{
Console.WriteLine("A total of {0} Bachelors student has tried to
enroll for History Course", BCount);
Console.WriteLine("A total of {0} Masters student has tried to enroll
for History Course", MCount);
}
protected
override bool
CheckQualified(Student student)
{
return
(((student.GPA > 3.0) &&
string.Compare(student.CourseName, "bachelor")>=0) ||
(student.GPA
> 3.2) &&
string.Compare(student.CourseName, "masters")>=0);
}
}
//Visitor
concrete method
class GeographyCourse : Course
{
//Implementation
of the visit method. The whole execution of the pattern lies
//in this
method. Changing the implementation of this method will not affect
// the
element classes'.
public override void Visit(CollegeTitle visitable)
{
Student
student = visitable as Student;
base.Visit(visitable);
if
(CheckQualified(student))
Console.WriteLine("Student {0} from {1} has been qualified to enroll
for geography course"
,
(student).Name
,
(student).CourseName);
}
public override void Count()
{
Console.WriteLine("A total of {0} Bachelors student has tried to
enroll for Geography Course", BCount);
Console.WriteLine("A total of {0} Masters student has tried to enroll
for Geography Course", MCount);
}
protected
override bool
CheckQualified(Student student)
{
return
(((student.GPA > 2.8) &&
string.Compare(student.CourseName, "bachelor")>=0) ||
(student.GPA
> 3.0) &&
string.Compare(student.CourseName, "masters")>=0);
}
}
public class Bachelor :Student
{
public
Bachelor(string name,double
gpa): base(name,gpa,"Bachelor")
{
}
}
public class Masters : Student
{
public
Masters(string name,double
gpa): base(name,gpa,"Masters")
{
}
}
//Element
public class Student : CollegeTitle
{
public string Name { get; set; }
public string CourseName { get;
set; }
public double GPA { get; set; }
public
Student(string name, double
gpa, string courseName)
{
this.Name
= name;
this.CourseName
= courseName;
this.GPA
= gpa;
}
public void Accept(ICourse
course)
{
course.Visit(this);
}
}
//Object
structure
class StudentCollection
{
List<Student> students = new
List<Student>();
public void AddStudent(Student
student)
{
if
(!students.Contains(student))
{
students.Add(student);
}
}
public void RemoveStudent(Student
student)
{
if(students.Contains(student))
{
students.Remove(student);
}
}
//iteratively
calling element accept method.
public void Accept(ICourse
course)
{
foreach
(Student student in
students)
{
student.Accept(course);
}
}
}
}
As you can
see, the most important method in the structure lies in the Implementation of
the Course.Visit() method. The core execution is done by hosting the implementation
on another class. Therefore, you can easily extend the capability of the
element class by adding another concrete visitor(in this case is XXCourse)implementation
which implement IVisitor(in this case is ICourse) interface.
Although
this is a very useful method, there are guidelines to using this:
• You have
a class hierarchy that is effectively sealed.
• There are
many distinct operations to perform on it.
• The
operations are orthogonal to the core purpose of the types in the hierarchy.
• You need
the flexibility to define new operations over time.
I
hope this post will give a better insight on visitor pattern. Do post your
review / better alternatives on this pattern.