24 03 2014
Visitor Pattern
As announced in my post about the “Null Object Pattern” I will explain the “Visitor Pattern” today. At the first time I maintained an application with visitors, I found it a bit confusing – Therefore I try to explain it based on my own experiences. The Visitor is a pattern of the category of behavior patterns which serves to seperate algorithms/logic from the object structure.
That means you define the processing of the object outside of it. In the UML diagramm we define a visitor interface (IVisitor) and an element interface (IElement). The conrete elements (ElementA, ElementB) implement that interface and the “Accept” method, that is essentially for the visitor behaviour.
The content of the concrete “Accept” method is simple. It gets an object that implements the “IVisitor” interface (VisitorX) and calls the “Visit” method of the visitor. So “ElementA” will call “Visit(ElementA element)”, “ElementB” will call “Visit(ElementB element)”, and so on. The visitor will call the “Accept” method of the object that implements “IElement” and is able to process the concrete implementation (ElementA or ElementB) in the defined method e.g. “Visit(ElementA element)”. This behaviour will allow you to make structured, type based processing of objects. If you want to have more Visitors you can simply create a new class that implements the IVisitor interface (e.g. VisitorY) without changing anything in the object structure (Elements).
At next we want to implement a visitor in our chess board example – but first I will show you a more practical example.
You can see, that we have two conrete objects called “UserObject” and “CommonObject” that implements the “IDbObject” interface. In our class “DbSaver” we need to handle the objects different. Therefore we implement the “IDbObjectVisitor” that will take over the seperation of the concrete “DbObject” type and we will implement the concrete save logic in the “Handle” Method for concrete “IDbObjects”. To make it more clear, take a look at the following example source code. Just create an instance of the “DbObjectController” and call “Execute” 😉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
public class VisitorExample { public interface IDbObject { void Accept(IDbObjectVisitor visitor); } public interface IDbObjectVisitor { void Handle(CommonObject commonObject); void Handle(UserObject userObject); } public interface IDbObjectSaver { void Save(List<IDbObject> dbObjects); } public class UserObject : IDbObject { public void Accept(IDbObjectVisitor visitor) { visitor.Handle(this); } } public class CommonObject : IDbObject { public void Accept(IDbObjectVisitor visitor) { visitor.Handle(this); } } public class DbObjectSaver : IDbObjectSaver, IDbObjectVisitor { public void Save(List<IDbObject> dbObjects) { foreach (IDbObject dbObject in dbObjects) { dbObject.Accept(this); } } public void Handle(CommonObject commonObject) { // DO SOMETHING WITH THE COMMON OBJECT Console.WriteLine("COMMON OBJECT"); } public void Handle(UserObject userObject) { // DO SOMETHING WITH THE USER OBJECT Console.WriteLine("USER OBJECT"); } } public class DbObjectController { private readonly IDbObjectSaver dbObjectSaver = new DbObjectSaver(); public void Execute() { List<IDbObject> dbObjects = new List<IDbObject>(); dbObjects.Add(new CommonObject()); dbObjects.Add(new UserObject()); dbObjects.Add(new CommonObject()); dbObjects.Add(new UserObject()); dbObjects.Add(new UserObject()); dbObjects.Add(new UserObject()); dbObjects.Add(new CommonObject()); this.dbObjectSaver.Save(dbObjects); } } } |
Please remember at this point, to the null object pattern. You should implement a class for an empty object instead of using “null” to prevent “NullReferenceException” in the “DbObjectSaver”.
You can also define the visitor interface with return values (generics are also possible, I will show this in the next example for our chess board).
In our chess board we have different figures, so it makes sense, that we implement the visitor for these figures. So let’s create an interface for the normal behaviour (void as return) and an interace with generic return value behaviour (I will show the usage later).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public interface IFigureVisitor { void HandleKing(King visitee); void HandleQueen(Queen visitee); void HandleKnight(Knight visitee); void HandleBishop(Bishop visitee); void HandleRook(Rook visitee); void HandlePawn(Pawn visitee); void HandleEmpty(Empty visitee); } public interface IFigureVisitor<T> { T HandleKing(King visitee); T HandleQueen(Queen visitee); T HandleKnight(Knight visitee); T HandleBishop(Bishop visitee); T HandleRook(Rook visitee); T HandlePawn(Pawn visitee); T HandleEmpty(Empty visitee); } |
Next we have to add the abstract “Accept” methods in the “Figure” class and make the concrete implementations in the conrete “Figure” classes. Because that’s always the same for each figure, I will only show the example for the class “King”.
1 2 |
public abstract void AcceptIFigureVisitor(IFigureVisitor visitor); public abstract T AcceptIFigureVisitor<T>(IFigureVisitor<T> visitor); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public sealed class King : Figure { public King(Color color) : base(color, () => Console.WriteLine("I'm a King!")) { } public override void AcceptIFigureVisitor(IFigureVisitor visitor) { visitor.HandleKing(this); } public override T AcceptIFigureVisitor<T>(IFigureVisitor<T> visitor) { return visitor.HandleKing(this); } } |
Now we are able to use the visitor. Let’s say we want to implement a “MoveFigure” functionality. We want a notification when a figure is killed by another, with the following output: Field, Figure and Color. For example “A8:Rook (Black) is killed by Rook (White)”. To get this behavior, we must implement the “IFigureVisitor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
public sealed class Field : IFigureVisitor<string> { private readonly int x; private readonly int y; private Figure figure; public Field(int x, int y, Figure figure = null) { this.figure = figure ?? new Empty(); this.x = x; this.y = y; } public Figure Figure { get { return this.figure; } set { string oldFigure = this.figure.AcceptIFigureVisitor(this); string newFigure = value.AcceptIFigureVisitor(this); if (!string.IsNullOrWhiteSpace(oldFigure) && !string.IsNullOrWhiteSpace(newFigure)) { Console.WriteLine("{0}:{1} is killed by {2}", this.Position, oldFigure, newFigure); } this.figure = value; } } public int X { get { return this.x; } } public int Y { get { return this.y; } } public char Letter { get { return (char)(this.x + 'A'); } } public int Number { get { return this.y + 1; } } public string Position { get { return string.Concat(this.Letter, this.Number.ToString(CultureInfo.InvariantCulture)); } } public override string ToString() { return "Hello! I'm a field!"; } public string HandleKing(King visitee) { // Make something with the king return string.Format("King ({0})", visitee.Color); } public string HandleQueen(Queen visitee) { // Make something with the queen return string.Format("Queen ({0})", visitee.Color); } public string HandleKnight(Knight visitee) { // Make something with the knight return string.Format("Knight ({0})", visitee.Color); } public string HandleBishop(Bishop visitee) { // Make something with the bishop return string.Format("Bishop ({0})", visitee.Color); } public string HandleRook(Rook visitee) { // Make something with the rook return string.Format("Rook ({0})", visitee.Color); } public string HandlePawn(Pawn visitee) { // Make something with the pawn return string.Format("Pawn ({0})", visitee.Color); } public string HandleEmpty(Empty visitee) { // Make something or nothing with the empty return string.Empty; } } |
Finally lets add the “MoveFigure” method to the “Board” class and use it 🙂 The Result will be: “A5:Pawn (Black) is killed by Pawn (White)”
1 2 3 4 5 |
public void MoveFigure(Field from, Field to) { to.Figure = from.Figure; from.Figure = new Empty(); } |
1 2 3 4 5 6 |
Board chessBoard = Board.GetInstance(); chessBoard.MoveFigure(chessBoard["A2"], chessBoard["A3"]); chessBoard.MoveFigure(chessBoard["A7"], chessBoard["A6"]); chessBoard.MoveFigure(chessBoard["A3"], chessBoard["A4"]); chessBoard.MoveFigure(chessBoard["A6"], chessBoard["A5"]); chessBoard.MoveFigure(chessBoard["A4"], chessBoard["A5"]); |
That’s all! You maybe have noticed that the implementation for the visitor pattern is coupled at some effort. So use it wisely. The other point is that you should use it only for closed systems and not for object structures that offer inheritance (outside his scope), because it is necessary that you adapt the visitor interface. Let’s say we have our “DbObject” example in a external library and the user wants to add a new “DbObject” like (e.g. GroupObject) that implements the “IDbOjects” interface. He will not be able to add the visitor behaviour because the necessary interface is in the external library. Also he can not implement a new Visitor interface because for this he must add a new “Accept” method in the existing “DbObjects”. That is the main reason why the visitor pattern is not used as often as other patterns – the extensibility. So take your time to find out the best use case for you 😉
Fluent Interface Pattern Basic datatypes in TTCN-3