Back to Thoughts On Technology Page | Back to Peter's Home Page | Back to Family Home Page



The Visitor Design Pattern
Peter Rose - 01/2005


ABSTRACT

SUMMARY

EXAMPLE OBJECT GRAPH

THE VISITOR AND VISITABLE OBJECTS

HOW THE PATTERN WORKS

THE ORDER AND ITEM VISITABLE CLASSES

THE ITEMPRICEADJUSTMENTVISITOR VISITOR CLASS

APPENDIX: SAMPLE PROGRAM OUTPUT

APPENDIX: FULL CODE




Abstract
The Visitor Design Pattern enables a cleaner separation of data objects from the business logic that uses that data. Whereas a session business object uses the data object to perform work, the Visitor Pattern is used to manipulate the internal data of the object for some external business purpose.

Summary
This article will explore the use of the Visitor Pattern as it relates to applying a business service to an object graph of aggregate objects as opposed to changing the underlying code of the objects involved. Most examples of the Visitor Pattern show traversing through data structures such as linked lists, however, as application programmers we tend not to deal with raw data structures as much as we do collections of objects in inheritance and aggregation relationships.

The example object graph used to demonstrate the Visitor Pattern will consist of a group of Order objects that each contain a Collection of Item objects. We will then construct an ItemPriceAdjustmentVisitor whos job it will be to change all of the prices of a given Order's Items by an increase of 1.33 if the Item's price is below $22.00.

Example Object Graph
Our simple object graph consists of a Collection of Order objects each aggregating a Collection of Item objects as shown below.

diagram

The Visitor and Visitable Objects
The Visitor Design Pattern uses to interfaces to form its structure: Visitor and Visitable.

diagram

A visitor object implements a Visitor interface which at minimum has a visit() method.

diagram

The Order and Item objects - which implement a Visitable interface - both have an accept() method.

diagram

A Visitable object accepts a Visitor (i.e. this.accept(Visitor visitor)), and a Visitor visits a Visitable object (i.e. visitor.visit(this)).

These interfaces are very simple and are shown below.


// ************************************************************************
/*
 * Visitor.java
 *
 * Provides a simple Visitor interface for the Visitor Design Pattern Example.
*/
// ************************************************************************
interface Visitor {
    public void visit(Object o);
} //endinterface: Visitor.java


// ************************************************************************
/*
 * Visitable.java
 *
 * Provides a simple Visitable interface for the Visitor Design Pattern Example.
*/
// ************************************************************************
interface Visitable {
    public void accept(Visitor visitor);
} //endinterface: Visitable


How The Pattern Works
A Visitor is like a plumber who is called to a house to fix something. The house is "visitable", i.e. it allows Visitors to enter. However, it doesn't know how to fix the plumbing, so it calls a plumber. The plumber, however, doesn't know anything about the house. It only knows about plumbing. The house knows about itself, so when the Visitor comes in the house, the house doesn't even know whether the Visitor is a plumber or electrician or painter. But it does know that it has rooms.

So, when a Visitor enters the house (programatically through the house's accept(Visitor visitor) method), it allows the Visitor to look at it first. It then shows (i.e. passes) the Visitor to each room in the house (through each room's own accept(Visitor visitor) method). When the Visitor enters a room (as it did when it entered the house itself), it knows if that room has plumbing or not. If no plumbing is in the room, the Visitor does nothing; if there is plumbing, then the Visitor does what it is programmed to do.

Interestingly, the way the Visitor checks each room to see if it is interested in it is to have the room pass itself into the Visitor's visit() method (i.e. visitor.visit(this)). The Visitor thus has access to the internals of that room/object. This is how it determines if it has any work to do with that particular object.

This process of the Visitor being passed in through the Visitable object's accept() method and then the Visitable object being passed into the Visitor through its visit() method is called "double dispatch".

The Order And Item Visitable Classes
Here are very simple stubs of the Order and Item classes:


// ************************************************************************
public class Order implements Visitable {
	private ArrayList items = null;
	private long orderId = 0;

	public void accept(Visitor visitor) {
		visitor.visit(this);
	} //endmethod: accept
} //endclass: Order


// ************************************************************************
public class Item implements Visitable {
	private long itemId = 0;
	private float price = 0.0f;

	public void accept(Visitor visitor) {
		visitor.visit(this);
	} //endmethod: accept()
} //endclass: Item


The ItemPriceAdjustmentVisitor Visitor Class
As per our business requirements, we want to create a Visitor class to "…change all of the prices of a given Order's Items by an increase of 1.33 if the Item's price is below $22.00". To clearly represent this requirement, we will name our Visitor ItemPriceAdjustmentVisitor.java. It will be coded as follows.


// ************************************************************************
class ItemPriceAdjustmentVisitor implements Visitor {
	private static final float TARGET_PRICE = 22.0f;
	private static final float ADJUSTMENT_PERCENTAGE = 1.33f;
	private static float currentPrice = 0.0f;
	private static float adjustedPrice = 0.0f;

	/* ====================================================
	 * Defines the business logic to perform on the target
	 * Visitable object via double-dispatch
	 * ==================================================== */
	public void visit(Object o) {
		if( o instanceof Item) {
			currentPrice = ((Item)o).getPrice();
			if(currentPrice <= TARGET_PRICE) {
				adjustedPrice = currentPrice*ADJUSTMENT_PERCENTAGE;
				((Item)o).setPrice(adjustedPrice);
				VisitorExample.log(3, "Price changed from " +
					currentPrice +
					" to " + ((Item)o).getPrice());
			} else {
				VisitorExample.log(3, "Current price " +
					currentPrice +
					" not adjusted as greater than min target price " +
					TARGET_PRICE);
			} //endif
		} else {
			VisitorExample.log(2, "I'm not an Item. Bye...");
		} //endif
	} //endmethod: visit()
} //endclass: ItemPriceAdjustmentVisitor


Notice the simplicity of this class. It is designed to do just one thing: make a price change where appropriate. We could also populate an instance HashMap or ArrayList property that would return to us a list of Item objects whose price was adjusted. Thus, we are completely flexible in our ability to manipulate the data in selected Item objects through a Visitor in anyway we see fit without altering the Item class itself.

Look closely at how the visit() method functions. The parameter is of type Object so that any Visitable object may pass itself into this visitor if its use could be functional somewhere else, i.e. reusable. For example, if we wanted to do something different in each Order object - such as return a list of orders visited for Item object price adjustment - the we could add an additional instanceof conditional for that purpose.

The beauty of double dispatch is that the Visitor has access to all of each Visitable object's internals without having to worry about which specific object it is dealing with. The business rules for the Visitor are totally encapsulated.

APPENDIX: Sample ItemPriceAdjustmentVisitor Output
The following is a sample run of the VisitorExample.java file


   ---> Order 1093884593337has added Item:
     ---> 1093884593337 has price: 5.5945673
     ---> 1093884593397 has price: 10.3134985
     ---> 1093884593397 has price: 13.273836
     ---> 1093884593397 has price: 5.081167
     ---> 1093884593397 has price: 0.09856973
     ---> 1093884593397 has price: 21.160076
     ---> 1093884593397 has price: 21.36883
     ---> 1093884593397 has price: 22.193434
     ---> 1093884593397 has price: 11.745681
     ---> 1093884593397 has price: 9.924277
   ---> Order 1093884593397has added Item:
     ---> 1093884593397 has price: 1.3464937
     ---> 1093884593397 has price: 3.3915036
     ---> 1093884593397 has price: 18.014051
     ---> 1093884593397 has price: 10.04461
     ---> 1093884593397 has price: 12.255216
     ---> 1093884593397 has price: 19.100582
     ---> 1093884593397 has price: 9.450298
     ---> 1093884593397 has price: 21.512775
     ---> 1093884593397 has price: 17.564783
     ---> 1093884593397 has price: 21.889627
   ---> Order 1093884593397has added Item:
     ---> 1093884593397 has price: 26.953852
     ---> 1093884593397 has price: 0.89273274
     ---> 1093884593397 has price: 18.268274
     ---> 1093884593397 has price: 8.069395
     ---> 1093884593397 has price: 8.195152
     ---> 1093884593397 has price: 17.3221
     ---> 1093884593397 has price: 6.099084
     ---> 1093884593397 has price: 26.05581
     ---> 1093884593397 has price: 14.329627
     ---> 1093884593397 has price: 3.6390615
   ---> ArrayList orderList contains orders = 3
   ---> Order 1093884593337 has accepted a visitor.
     ---> I'm not an Item. Bye...
     ---> Item 1093884593337 has accepted a visitor.
       ---> Price changed from 5.5945673 to 7.440775
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 10.3134985 to 13.716953
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 13.273836 to 17.654203
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 5.081167 to 6.7579527
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 0.09856973 to 0.13109775
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 21.160076 to 28.142902
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 21.36883 to 28.420544
     ---> Item 1093884593397 has accepted a visitor.
       ---> Current price 22.193434 not adjusted as greater than min target price 22.0
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 11.745681 to 15.621756
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 9.924277 to 13.199289
   ---> Order 1093884593397 has accepted a visitor.
     ---> I'm not an Item. Bye...
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 1.3464937 to 1.7908367
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 3.3915036 to 4.5106997
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 18.014051 to 23.958689
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 10.04461 to 13.359332
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 12.255216 to 16.299437
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 19.100582 to 25.403774
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 9.450298 to 12.568897
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 21.512775 to 28.611992
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 17.564783 to 23.361162
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 21.889627 to 29.113205
   ---> Order 1093884593397 has accepted a visitor.
     ---> I'm not an Item. Bye...
     ---> Item 1093884593397 has accepted a visitor.
       ---> Current price 26.953852 not adjusted as greater than min target price 22.0
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 0.89273274 to 1.1873345
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 18.268274 to 24.296806
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 8.069395 to 10.732296
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 8.195152 to 10.899553
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 17.3221 to 23.038393
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 6.099084 to 8.111782
     ---> Item 1093884593397 has accepted a visitor.
       ---> Current price 26.05581 not adjusted as greater than min target price 22.0
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 14.329627 to 19.058405
     ---> Item 1093884593397 has accepted a visitor.
       ---> Price changed from 3.6390615 to 4.839952


APPENDIX: Full Code


//-- VisitorExample.java

import java.util.ArrayList;


// ************************************************************************
/*
 * VisitorExample.java
 *
 * Provides a simple Visitor Design Pattern Example.
 *
 * The example will construct an object graph of several Order objects
 * each of which aggregates an ArrayList of Item objects. A Visitor will
 * be created which will traverse through the structure and adjust the
 * price of all Item objects.
*/
// ************************************************************************
public class VisitorExample {
	static int MAX_ORDERS = 3;
	static int MAX_ITEMS = 10;


	/** ====================================================
	 * Main used as main driver for the example
	 * ==================================================== */
	public static void main(String[] args) {
		Item item = null;
		Order order = null;

		VisitorExample example = new VisitorExample();

		//-- Create example's sample object graph
		ArrayList orderList = example.createSampleObjectGraph();
		VisitorExample.log(1,
			"ArrayList orderList contains orders = " +
			orderList.size());

		//-- Vist each Order and each Order's Items with the Visitor
		ItemPriceAdjustmentVisitor visitor =
			new ItemPriceAdjustmentVisitor();
		for(int i=0; i < orderList.size(); i++) {
			order = (Order)orderList.get(i);
			order.accept(visitor);
		} //endfor
	} //endmain


	/** ========================================================================
	 *
	 * Create example sample object graph
	 *
	 * ======================================================================== */
	private ArrayList createSampleObjectGraph() {
		Item item = null;
		Order order = null;

		//-- Create example sample object graph
		ArrayList orderList = new ArrayList();
		for(int i=0; i < VisitorExample.MAX_ORDERS; i++) {
			order = new Order();
			order.setOrderId((new java.util.Date()).getTime());
			ArrayList itemList = new ArrayList();
			VisitorExample.log(1, "Order " + order.getOrderId() +
					"has added Item:");
			for(int j=0; j < VisitorExample.MAX_ITEMS; j++) {
				item = new Item();
				item.setItemId((new java.util.Date()).getTime());
				item.setPrice(
				(new Double(java.lang.Math.PI*
					java.lang.Math.random()*8.6f)).floatValue());
				itemList.add(item);
				VisitorExample.log(2, "" + item.getItemId() +
						" has price: " + item.getPrice());
				//-- Pause to let seconds change for new ids...
				for(int wait=0; wait < 50000000; wait++) {}
			}//endfor
			order.setItemList(itemList);
			orderList.add(order);
		} //endfor

		return(orderList);
	} //endmethod: createSampleObjectGraph


	/** ========================================================================
	 *
	 * Used for debug testing
	 *
	 * ======================================================================== */
  public static void log(int aLevel, String aMsg) {
	  boolean showLogTraceToSystemOut = true;
	  if(showLogTraceToSystemOut) {
		  String prefix = "";
		  if(aLevel == 1) {
			  prefix = "   ---> ";
		  } else if(aLevel == 2) {
			  prefix = "     ---> ";
		  } else if(aLevel == 3) {
			  prefix = "       ---> ";
		  } //endif
		  System.out.println(prefix + aMsg);
	  } //endif
  } //endmethod: log
} //endclass: VisitorExample.java


// ************************************************************************
/*
 * Visitor.java
 *
 * Provides a simple Visitor interface for Visitor Design Pattern Example.
*/
// ************************************************************************
interface Visitor {
    public void visit(Object o);
} //endinterface: Visitor.java


// ************************************************************************
/*
 * Visitable.java
 *
 * Provides a simple Visitable interface for Visitor Design Pattern Example.
*/
// ************************************************************************
interface Visitable {
    public void accept(Visitor visitor);
} //endinterface: Visitable



// ************************************************************************
/*
 * Order.java
 *
 * Order object is a container for ArrayList of associated item objects.
*/
// ************************************************************************
class Order implements Visitable {
	private ArrayList items = null;
	private long orderId = 0;

	/* ====================================================
	 * Accepts a Visitor object
	 * ==================================================== */
	public void accept(Visitor visitor) {
		VisitorExample.log(1, "Order " + this.getOrderId() +
				" has accepted a visitor.");
		visitor.visit(this);

		//-- Have the Visitor vist all of my aggregates
		Item item = null;
		for(int i=0; i < items.size(); i++) {
			item = (Item)items.get(i);
			item.accept(visitor);
		} //endfor
	} //endmethod: accept()

	public long getOrderId() { return(orderId); }
	public void setOrderId(long aOrderId) { orderId = aOrderId; }
	public ArrayList getItemList() { return(items); }
	public void setItemList(ArrayList aItems) { items = aItems; }
} //endclass: Order


// ************************************************************************
/*
 * Item.java
 *
 * An Item is an aggregate object of an Order object.
*/
// ************************************************************************
class Item implements Visitable {
	private long itemId = 0;
	private float price = 0.0f;

	/* ====================================================
	 * Accepts a Visitor object
	 * ==================================================== */
	public void accept(Visitor visitor) {
		VisitorExample.log(2, "Item "	+ this.getItemId() +
				" has accepted a visitor.");
		visitor.visit(this);
	} //endmethod: accept()

	public long getItemId() { return(itemId); }
	public void setItemId(long aItemId) { itemId = aItemId; }
	public float getPrice() { return(price); }
	public void setPrice(float aPrice) { price = aPrice; }
} //endclass: Item


// ************************************************************************
/*
 * ItemPriceAdjustmentVisitor.java
 *
 * Business service object to change all of the prices of a given Order’s
 * Items by an increase of 1.33 if the Item’s price is below $22.00.
*/
// ************************************************************************
class ItemPriceAdjustmentVisitor implements Visitor {
	private static final float TARGET_PRICE = 22.0f;
	private static final float ADJUSTMENT_PERCENTAGE = 1.33f;
	private static float currentPrice = 0.0f;
	private static float adjustedPrice = 0.0f;

	/* ====================================================
	 * Defines the business logic to perform on the target
	 * Visitable object via double-dispatch
	 * ==================================================== */
	public void visit(Object o) {
		if( o instanceof Item) {
			currentPrice = ((Item)o).getPrice();
			if(currentPrice <= TARGET_PRICE) {
				adjustedPrice = currentPrice*ADJUSTMENT_PERCENTAGE;
				((Item)o).setPrice(adjustedPrice);
				VisitorExample.log(3, "Price changed from " +
					currentPrice +
					" to " + ((Item)o).getPrice());
			} else {
				VisitorExample.log(3, "Current price " +
					currentPrice +
					" not adjusted as greater than min target price " +
					TARGET_PRICE);
			} //endif
		} else {
			VisitorExample.log(2, "I'm not an Item. Bye...");
		} //endif
	} //endmethod: visit()
} //endclass: ItemPriceAdjustmentVisitor



Back to Thoughts On Technology Page | Back to Peter's Home Page | Back to Family Home Page