The Visitor Pattern
1
The Problem
2
The Problem
Show me everything in a consistent manner …
… and send it to my friend via email
oh, and send it to her phone as well
and print it …
3
Some analysis music please...
AmazoneNote
title
author
isbn
price
GoogleNote
heading
excerpt
url
NewsNote
heading
byLine
excerpt
RssNote
heading
text
date
4
Commonality is in the eye of the beholder
Note
AmazoneNote
GoogleNote
title
author
isbn
price
heading
excerpt
url
NewsNote
heading
byLine
excerpt
RssNote
heading
text
date
5
Need do operations in Note structure
• With a little casual application of polymorphism...
Note
printHeading()
printDetails()
asPrintable()
asEmailContent()
asSMS()
Does this look bad to you?
…
AmazoneNote
GoogleNote
NewsNote
RssNote
6
The solution
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"
GOF
7
Structure
Visitor
Client
visitConcreteElement1(e : ConcreteElement1)
visitConcreteElement2(e : ConcreteElement2)
ConcreteVisistor1
ConcreteVisistor2
visitConcreteElement1(e : ConcreteElement1)
visitConcreteElement2(e : ConcreteElement2)
visitConcreteElement1(e : ConcreteElement1)
visitConcreteElement2(e : ConcreteElement2)
ObjectStructure
Element
accept(v : Visitor)
visitor.visitConcreteElement1(this);
ConcreteElement1
ConcreteElement2
accept(visitor : Visitor)
accept(visitor : Visitor)
8
A solution to a problem in a context
Note
accept(v : NodeVisitor)
AmazoneNote
GoogleNote
NewsNote
RssNote
<<Interface>>
NoteVisitor
+ visitAmazoneNote()
+ visitGoogleNote()
+ visitNewsNote()
+ visitRssNote()
NoteHeadingPrinter
NoteDetailsPrinter
9
Let’s show it in code!
public interface Note {
void accept(NoteVisitor v);
}
public interface NoteVisitor {
void visitAmazoneNote(AmazoneNote note);
void visitGoogleNote(GoogleNote note);
void visitNewsNote(NewsNote note);
void visitRssNote(RssNote note);
}
10
AmazoneNote
public class AmazoneNote implements Note {
String title;
String author;
String isbn;
double price;
public AmazoneNote(String title, String author,
String isbn, double price) {
this.title = title;
this.author = author;
this.isbn = isbn;
this.price = price;
}
public void accept(NoteVisitor visitor) {
visitor.visitAmazoneNote(this);
}
}
11
GoogleNote
public class GoogleNote implements Note {
String heading;
String excerpt;
String url;
public GoogleNote(String heading, String excerpt,
String url) {
this.heading = heading;
this.excerpt = excerpt;
this.url = url;
}
public void accept(NoteVisitor visitor) {
visitor.visitGoogleNote(this);
}
}
12
NewsNote
public class NewsNote implements Note {
String heading;
String byLine;
String excerpt;
public NewsNote(String heading, String byLine,
String excerpt) {
this.heading = heading;
this.byLine = byLine;
this.excerpt = excerpt;
}
public void accept(NoteVisitor visitor) {
visitor.visitNewsNote(this);
}
}
13
RssNote
public class RssNote implements Note {
String heading;
String text;
Date date;
public RssNote(String heading, String text) {
this.heading = heading;
this.text = text;
this.date = new Date(System.currentTimeMillis());
}
public void accept(NoteVisitor visitor) {
visitor.visitRssNote(this);
}
}
14
NoteHeadingPrinter visitor
public class NoteHeadingPrinter implements NoteVisitor {
public void visitAmazoneNote(AmazoneNote note) {
System.out.println(note.title);
}
public void visitGoogleNote(GoogleNote note) {
System.out.println(note.heading);
}
public void visitNewsNote(NewsNote note) {
System.out.println(note.heading);
}
public void visitRssNote(RssNote note) {
System.out.println(note.heading);
}
}
15
NoteDetailsPrinter visitor
public class NoteDetailsPrinter implements NoteVisitor {
public void visitAmazoneNote(AmazoneNote note) {
System.out.println(note.title);
System.out.println(note.price);
}
public void visitGoogleNote(GoogleNote note) {
System.out.println(note.heading);
System.out.println(note.excerpt);
}
public void visitNewsNote(NewsNote note) {
System.out.println(note.heading);
System.out.println(note.byLine);
System.out.println(note.excerpt);
}
}
public void visitRssNote(RssNote note) {
System.out.println(note.heading);
System.out.println(note.text);
}
16
NoteDetailsPrinter visitor
public class NoteDetailsPrinter implements NoteVisitor {
public void visitAmazoneNote(AmazoneNote note) {
System.out.println(note.title);
System.out.println(note.price);
}
public void visitGoogleNote(GoogleNote note) {
System.out.println(note.heading);
System.out.println(note.excerpt);
}
public void visitNewsNote(NewsNote note) {
System.out.println(note.heading);
System.out.println(note.byLine);
System.out.println(note.excerpt);
}
}
public void visitRssNote(RssNote note) {
System.out.println(note.heading);
System.out.println(note.text);
}
17
NoteIterator
public class NoteIterator {
private List<Note> sampleNotes() {
List<Note> notes = new ArrayList<Note>();
notes.add(new NewsNote("Melbourne Pushes Boundaries", "Tim
Colebatch",
"Melbourne is experiencing its biggest growth surge since
1960's"));
notes.add(new GoogleNote(
"Redhill Consulting Pty Ltd - Ruby on Rails plugins",
"Here are some Ruby on Rails plugins we're
developed ...",
" />notes.add(new AmazoneNote("The Wolves in the Wall", "Nail
Gaiman",
"ISBN XXX-XXXX-XXXXX", 12.06));
notes.add(new RssNote("let's talk about tests, baby...",
"Some long babbling about test naming heuristics"));
return notes;
}
public void visitAllUsing(NoteVisitor visitor) {
for (Note note : sampleNotes()) {
note.accept(visitor);
18
Test Drive
public class NoteTestDrive {
public static void main(String[] args) {
NoteIterator iterator = new NoteIterator();
iterator.visitAllUsing(new NoteHeadingPrinter());
}
}
iterator.visitAllUsing(new NoteDetailsPrinter());
19
Output
Melbourne Pushes Boundaries
The Wolves in the Wall
Redhill Consulting Pty Ltd - Ruby on Rails Plugins
Output of
NoteHeadingPrinter
Let's talk about tests, baby...
Melbourne Pushes Boundaries
Tim Colebatch
Melbourne is experiencing its biggest growth surge since the 1960's
The Wolves in the Wall
12.06
Output of NoteDetailsPrinter
Redhill Consulting Pty Ltd - Ruby on Rails Plugins
Here are some Ruby on Rails plugins we've developed...
Let's talk about tests, baby...
Some long babbling about test naming heuristics
20
When?
• “An object structure contains many classes of objects
with differing interfaces, and you want to perform
operations on these objects that depend on their
concrete classes”
• “Many distinct and unrelated operations need to be
performed on objects in an object structure, and you
want to avoid ‘polluting’ their classes with these
operations. Visitor lets you keep unrelated operations
together by defining them in one class. When the object
structure is shared by many applications, use Visitor to
put operations in just those applications that need them”
21
When?
• “The classes defining the object structure rarely
change, but you often want to define new
operations over the structure. Changing the
object structure classes requires redefining the
interface to all visitors, which is potentially costly.
If the object structure classes change often, then
it’s probably better to define the operations in
those classes”
22
Consequences
• Visitor makes adding new operations easy
• A visitor gathers related operations and
separates unrelated ones
• Adding new ConcreteElement classes is hard
• Visitors can cross object hierarchies
• Visitors can accumulate state
• Visitors may compromise encapsulation
23
File Folder Examples
FileSystemElement
name : String
getName()
getElement(name)
createIterator()
accept(v : Vistor)
File
size : int
Folder
children : ArrayList
getElement()
createIterator()
accept()
addElement(FileSystemElement)
removeElement(FileSystemElement)
getElement(name)
createIterator()
accept(v : Visitor)
Visitor
visitFile(file : File)
visitFolder(folder : Folder)
PrintVisitor
CountFileVisitor
visitFile(file : File)
visitFolder(folder : Folder)
visitFile(file : File)
visitFolder(folder : Folder)
24
Shape Example
Shape
Client
location : CartesianPoint
accept()
Dot
Circle
Square
radius : int
size : int
Rectangle
heigth : int
width : int
<<Interface>>
ShapeVisitor
forDot()
forCircle()
forSquare()
forRectanle()
Area
IsContain
point : CartesianPoint
BoundingBox
25