Gang of Four Behavioural pattern: Visitor
Behavioural Pattern
Defines a new operation to a class without change. 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.
To get the code for this example:
git clone https://github.com/spotadev/gangoffour.git
In src/main/java navigate to this package:
com.javaspeak.designpatterns.go4.behavioural.visitor
You can run the code from the main method of:
VisitorApplication
The output of running the example looks like:
============================================
SmallShapeVisitor
============================================
T
TTT
TTTTT
xxxx
xxxx
xxxx
============================================
BigShapeVisitor
============================================
T
TTT
TTTTT
TTTTTTT
TTTTTTTTT
Triangle
xxxxxxxx
xxxxxxxx
xxxxxxxx
xxxxxxxx
xxxxxxxx
Square
There are two Visitors, one is called BigShapeVisitor and the other SmallShapeVisitor. As the names imply BigShapeVisitor is responsible for drawing big Shapes and SmallShapeVisitor is responsible for drawing small shapes.
It make senses to centralise the code for drawing Big Shapes in one place and the code for drawing small shapes in another.
As time goes on we may want to create new Visitors like "ShinyShapeVisitor" or "3DShapeVisitor".
Instead of having to go and edit many different shape classes to produce a different version of their shape a new Visitor can be created and the code placed in one centralised place.
When we call the accept( ShapeVisitor shapeVisitor ) method on a TriangleVisitable or SquareVisitable we pass it an implementation of ShapeVisitor. Implementations include BigShapeVisitor or SmallShapeVisitor. Internal to the accept method of TriangleVisitable or SquareVisitable you will see the visit method is called on the Visitor passing a reference of this:
// inside TriangleVisitable or SquareVisitable
public void accept( ShapeVisitor shapeVisitor ) {
shapeVisitor.visit( this );
}
In the Visitor you will find overloaded visit methods, one for each Visitable
// inside BigShapeVisitor or SmallShapeVisitor
public void visit( TriangleVisitable triangleVisitable ) {
// ... code goes here to draw the big or small version of a Triangle
// depending on whether this class is a BigShapeVisitor or
// SmallShapeVisitor
}
public void visit( SquareVisitable squareVisitable ) {
// ... code here to draw big or small version of a Square
// depending on whether this class is a BigShapeVisitor or
// SmallShapeVisitor
}
SquareVisitable and TriangleVisitable are Visitables. These classes may hold information useful for all Visitors. This information may be different between between the Visitables as it can be specific to the Visitable itself. For example the TriangleVisitable has a method "getCharacter()" which allows the BigShapeVisitor and SmallShapeVisitor to know what character to draw the triangle with. Note that SquareVisitable does not have a "getCharacter()" method but instead has a personalized "getTitle()" method.
package com.javaspeak.designpatterns.go4.behavioural.visitor;
/**
* Text book description:
* <ul>
* Visitor: Defines a new operation to a class without change. 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.
* </ul>
* This example uses the Visitor Pattern.
* <p>
* The output of running the example looks like:
* <pre>
* ============================================
* SmallShapeVisitor
* ============================================
* T
* TTT
* TTTTT
*
* xxxx
* xxxx
* xxxx
*
* ============================================
* BigShapeVisitor
* ============================================
* T
* TTT
* TTTTT
* TTTTTTT
* TTTTTTTTT
*
* Triangle
*
* xxxxxxxx
* xxxxxxxx
* xxxxxxxx
* xxxxxxxx
* xxxxxxxx
*
* Square
* </pre>
* <p>
* There are two Visitors, one is called BigShapeVisitor and the other SmallShapeVisitor. As
* the names imply BigShapeVisitor is responsible for drawing big Shapes and SmallShapeVisitor
* is responsible for drawing small shapes.
* <p>
* It make senses to centralise the code for drawing Big Shapes in one place and the code for
* drawing small shapes in another.
* <p>
* As time goes on we may want to create new Visitors like "ShinyShapeVisitor" or "3DShapeVisitor".
* <p>
* Instead of having to go and edit many different shape classes to produce a different version of
* their shape a new Visitor can be created and the code placed in one centralised place.
* <p>
* When we call the accept( ShapeVisitor shapeVisitor ) method on a TriangleVisitable or
* SquareVisitable we pass it an implementation of ShapeVisitor. Implementations include
* BigShapeVisitor or SmallShapeVisitor. Internal to the accept method of TriangleVisitable or
* SquareVisitable you will see the visit method is called on the Visitor passing a reference of this:
* <pre>
* // inside TriangleVisitable or SquareVisitable
* public void accept( ShapeVisitor shapeVisitor ) {
*
* shapeVisitor.visit( this );
* }
* </pre>
* In the Visitor you will find overloaded visit methods, one for each Visitable
* <pre>
* // inside BigShapeVisitor or SmallShapeVisitor
* public void visit( TriangleVisitable triangleVisitable ) {
*
* // ... code goes here to draw the big or small version of a Triangle
* // depending on whether this class is a BigShapeVisitor or
* // SmallShapeVisitor
* }
*
* public void visit( SquareVisitable squareVisitable ) {
*
* // ... code here to draw big or small version of a Square
* // depending on whether this class is a BigShapeVisitor or
* // SmallShapeVisitor
* }
* </pre>
* SquareVisitable and TriangleVisitable are Visitables. These classes may hold information
* useful for all Visitors. This information may be different between between the Visitables
* as it can be specific to the Visitable itself. For example the TriangleVisitable has a
* method "getCharacter()" which allows the BigShapeVisitor and SmallShapeVisitor to know what
* character to draw the triangle with. Note that SquareVisitable does not have a "getCharacter()"
* method but instead has a personalized "getTitle()" method.
* <p>
* @author John Dickerson - 22 Feb 2020
*/
public class VisitorApplication {
private void runExample() {
ShapeVisitor[] shapeVisitors =
new ShapeVisitor[] { new SmallShapeVisitor(), new BigShapeVisitor() };
ShapeVisitable[] shapeVisitables =
new ShapeVisitable[] { new TriangleVisitable(), new SquareVisitable() };
for ( ShapeVisitor shapeVisitor : shapeVisitors ) {
System.out.println( "============================================" );
System.out.println( shapeVisitor.getName() );
System.out.println( "============================================" );
for ( ShapeVisitable shapeVisitable : shapeVisitables ) {
shapeVisitable.accept( shapeVisitor );
}
}
}
public static void main( String[] args ) {
VisitorApplication application = new VisitorApplication();
application.runExample();
}
}
package com.javaspeak.designpatterns.go4.behavioural.visitor;
/**
* SquareVisitable and TriangleVisitable implement this class. The calling code in
* ApplicationVisitor calls the accept method on SquareVisitable and TriangleVisitable two times
* each. On once occasion it calls passing the SmallShapeVisitor through the accept method and the
* second time it passes the BigShapeVisitor. Both SquareVisitable and TriangleVisitable in turn
* call the visit method on the Visitor classes, SmallShapeVisitor and BigShapeVisitor.
* <p>
* The result is a small triangle is drawn when passing the SmallShapeVisitor into the accept
* method of TriangleVisitable and similarly a small square is drawn when passing the
* SmallShapeVisitor into the accept method of SquareVisitable.
* <p>
* Both the small triangle and small square drawing code is encapsulated in the SmallShapeVisitor
* instead of being in both TriangleVisitable and SquareVisitable. What this means is that the
* TriangleVisitable and SquareVisitable code does not need to change if a new style of drawing a
* triangle and square (such as in 3D) is invented. The 3D implementation of both a triangle and
* square could then be encapsulated in a new Visitor called 3DShapeVisitor.
* <p>
* A big triangle is drawn passing the BigShapeVisitor into the the accept method of
* TriangleVisitable and similarly a big square is drawn passing the BigShapeVisitor into the accept
* method of SquareVisitable. Both the big triangle and big square drawing code is encapsulated
* in the BigShapeVisitor instead of being in both TriangleVisitable and SquareVisitable. This makes
* it easy to maintain code relating to big shapes across all shapes.
*
* @author John Dickerson - 22 Feb 2020
*/
public interface ShapeVisitable {
/**
* The Calling code, ApplicationVisitor, calls the accept method on TriangleVisitable or
* SquareVisitable passing in SmallShapeVisitor or BigShapeVisitor. The accept implementation
* will in turn call one of the overloaded visit methods of SmallShapeVisitor or BigShapeVisitor
*
* @param shapeVisitor
* The ShapeVisitor, either SmallShapeVisitor or BigShapeVisitor, to call the overloaded
* visit method on, and in so doing draw a shape.
*/
void accept( ShapeVisitor shapeVisitor );
}
package com.javaspeak.designpatterns.go4.behavioural.visitor;
/**
* The calling code, ApplicationVisitor calls the accept method passing in a SmallShapeVisitor or
* BigShapeVisitor. The accept method in turn calls the overloaded visit method of SmallShapevisitor
* or BigShapeVisitor.
*
* @author John Dickerson - 22 Feb 2020
*/
public class TriangleVisitable implements ShapeVisitable {
/**
* This method is specific to SquareVisitable and is not in SquareVisitable. Note that a
* reference to this class is passed into the accept(..) method as "this". Internally the
* Visitors, SmallShapeVisitor and BigShapeVisitor call getCharacters() to get the letter used
* to render the Triangle.
*
* @return Title of Square
*/
String getCharacter() {
return "T";
}
@Override
public void accept( ShapeVisitor shapeVisitor ) {
shapeVisitor.visit( this );
}
}
package com.javaspeak.designpatterns.go4.behavioural.visitor;
/**
* The calling code, ApplicationVisitor calls the accept method passing in a SmallShapeVisitor or
* BigShapeVisitor. The accept method in turn calls the overloaded visit method of SmallShapevisitor
* or BigShapeVisitor.
*
* @author John Dickerson - 22 Feb 2020
*/
public class SquareVisitable implements ShapeVisitable {
/**
* This method is specific to SquareVisitable and is not in TriangleVisitable. Note that a
* reference to this class is passed into the accept(..) method as "this". Internally the
* Visitors, SmallShapeVisitor and BigShapeVisitor call getTitle() to add a title to the Square.
*
* @return Title of Square
*/
String getTitle() {
return "Square";
}
@Override
public void accept( ShapeVisitor shapeVisitor ) {
shapeVisitor.visit( this );
}
}
package com.javaspeak.designpatterns.go4.behavioural.visitor;
/**
*
* Both BigShapeVisitor and SmallShapeVisitor implement this interface.
* <p>
* BigShapeVisitor encapsulates the code for drawing big triangles and big squares while
* SmallShapeVisitor encapsulates the code for drawing small triangles and small square.
* <p>
* The visitors, BigShapeVisitor and SmallShapeVisitor make it easier to maintain code relating to
* big and small shapes.
* <p>
* Calling accept on a TriangleVisitable or SquareVisitable delegates the call to the visitor passed
* in as a method argument to the accept method. The visit method is called on the ShapeVisitor,
* BigShapeVisitor or SmallShapeVisitor.
* <p>
* Notice that the visit methods are overloaded and once takes in a TriangleVisitable and the other
* a SquareVisitable.
*
* @author John Dickerson - 22 Feb 2020
*/
public interface ShapeVisitor {
/**
* Gets the name of the ShapeVisitor, e.g. SmallShapeVisitor or BigShapeVisitor
*
* @return
* name of the ShapeVisitor
*/
String getName();
/**
* Calling accept on a TriangleVisitable delegates the call to the visitor passed in as a
* method argument to the accept method. The visit method is called on the ShapeVisitor:
* BigShapeVisitor or SmallShapeVisitor. BigShapeVisitor and SmallShapeVisitor implement this
* ShapeVisitor interface and do the actual drawing of the triangle.
*
* @param triangleVisitable
* The TriangleVisitable to retrieve triangle properties from such as the character to
* use to draw the triangle with.
*/
void visit( TriangleVisitable triangleVisitable );
/**
* Calling accept on a SquareVisitable delegates the call to the visitor passed in as a method
* argument to the accept method. The visit method is called on the ShapeVisitor,
* BigShapeVisitor or SmallShapeVisitor. BigShapeVisitor and SmallShapeVisitor implement this
* ShapeVisitor interface and do the actual drawing of the square.
*
* @param squareVisitable
* The SquareVisitable to retrieve triangle properties from such as the character to use
* to draw the triangle with.
*/
void visit( SquareVisitable squareVisitable );
}
package com.javaspeak.designpatterns.go4.behavioural.visitor;
/**
* BigShapeVisitor is responsible for drawing big Shapes.
* <p>
* It make senses to centralise the code for drawing Big Shapes.
* <p>
* <p>
* As time goes on we may want to create new Visitors like "ShinyShapeVisitor" or "3DShapeVisitor".
* <p>
* Instead of having to go and edit many different shape classes to produce a different version of
* their shape a new Visitor can be created and the code placed in one centralised place.
* <p>
* When we call the "accept( ShapeVisitor shapeVisitor )" method on a TriangleVisitable or
* SquareVisitable we pass it an implementation of ShapeVisitor such as this BigShapeVisitor.
* <p>
* Internal to the accept method of TriangleVisitable or SquareVisitable you will see the visit
* method is called on the Visitor (e.g. this class)
*
* @author John Dickerson - 22 Feb 2020
*/
public class BigShapeVisitor implements ShapeVisitor {
@Override
public String getName() {
return "BigShapeVisitor";
}
@Override
public void visit( TriangleVisitable triangleVisitable ) {
StringBuilder shapeSB = new StringBuilder();
shapeSB.append( " x \n" );
shapeSB.append( " xxx \n" );
shapeSB.append( " xxxxx \n" );
shapeSB.append( " xxxxxxx \n" );
shapeSB.append( "xxxxxxxxx\n" );
shapeSB.append( "\nTriangle\n" );
String characterToBuild = triangleVisitable.getCharacter();
String shape = shapeSB.toString();
shape = shape.replaceAll( "x", characterToBuild );
System.out.println( shape );
}
@Override
public void visit( SquareVisitable squareVisitable ) {
StringBuilder shapeSB = new StringBuilder();
shapeSB.append( "xxxxxxxx\n" );
shapeSB.append( "xxxxxxxx\n" );
shapeSB.append( "xxxxxxxx\n" );
shapeSB.append( "xxxxxxxx\n" );
shapeSB.append( "xxxxxxxx\n" );
shapeSB.append( "\n" );
shapeSB.append( squareVisitable.getTitle() ).append( "\n" );
System.out.println( shapeSB.toString() );
}
}
package com.javaspeak.designpatterns.go4.behavioural.visitor;
/**
* SmallShapeVisitor is responsible for drawing small Shapes.
* <p>
* It make senses to centralise the code for drawing Small Shapes.
* <p>
* <p>
* As time goes on we may want to create new Visitors like "ShinyShapeVisitor" or "3DShapeVisitor".
* <p>
* Instead of having to go and edit many different shape classes to produce a different version of
* their shape a new Visitor can be created and the code placed in one centralised place.
* <p>
* When we call the "accept( ShapeVisitor shapeVisitor )" method on a TriangleVisitable or
* SquareVisitable we pass it an implementation of ShapeVisitor such as this SmallShapeVisitor.
* <p>
* Internal to the accept method of TriangleVisitable or SquareVisitable you will see the visit
* method is called on the Visitor (e.g. this class)
*
* @author John Dickerson - 22 Feb 2020
*/
public class SmallShapeVisitor implements ShapeVisitor {
@Override
public String getName() {
return "SmallShapeVisitor";
}
@Override
public void visit( TriangleVisitable triangleVisitable ) {
StringBuilder shapeSB = new StringBuilder();
shapeSB.append( " x \n" );
shapeSB.append( " xxx \n" );
shapeSB.append( "xxxxx\n" );
String characterToBuild = triangleVisitable.getCharacter();
String shape = shapeSB.toString();
shape = shape.replaceAll( "x", characterToBuild );
System.out.println( shape );
}
@Override
public void visit( SquareVisitable squareVisitable ) {
StringBuilder shapeSB = new StringBuilder();
shapeSB.append( "xxxx\n" );
shapeSB.append( "xxxx\n" );
shapeSB.append( "xxxx\n" );
shapeSB.append( "\n" );
System.out.println( shapeSB.toString() );
}
}
Back: Gang of Four
Page Author: JD