How to implement Visitor pattern in Java?

Let’s say you are running a shop.

You sell books, phones , shirts and other stuff.

You already have a software to manage your stock.

You store the original price, selling price , quantity and name of each item in your database.

One day you decide to launch a massive discount sales.

And you want to project how much profit you will likely get from the sales.

What do you do for your application to calculate that ?

Are you going to change your existing code to add this logic?

That is not an effective solution , you don’t want to break existing code.

Plus you may want to remove this feature once you use it.

Here comes Visitor pattern to the rescue!

Visitor pattern lets you to add additional responsibility to a group of objects without changing existing code.

Let’s see how we can apply this to our use case.

Let’s make the following assumptions:

  • You want to calculate the projected profit for book , phone and shirt sales
  • You have a different discount percentage for each item :
    • Book – 40 %
    • Phone – 20%
    • Shirt – 50%
  • You expected different projected number of sales for each item:
    • Book – 80%
    • Phone – 90%
    • Shirt – 75%

All these configuration is done inside a separate class in Visitor pattern.

Also the logic to handle projected sales for each item is calculated inside this class.

Now let’s dive into the client code:

package behavioural.visitor.pattern;

import java.util.ArrayList;
import java.util.List;

public class Client {

	public static void main(String a[]) {

		Book book = new Book();
		book.setName("The History of Western Philosophy - Betrand Russell");
		book.setOriginalPrice(500);
		book.setSellingPrice(1000);
		book.setQuantity(40);

		Phone phone = new Phone();
		phone.setName("Iphone");
		phone.setOriginalPrice(40000);
		phone.setSellingPrice(65000);
		phone.setQuantity(50);

		Shirt shirt = new Shirt();
		shirt.setName("Polo Tshirt - Size XL");
		shirt.setOriginalPrice(200);
		shirt.setSellingPrice(500);
		shirt.setQuantity(100);

		List<Item> items = new ArrayList<Item>();
		items.add(book);
		items.add(phone);
		items.add(shirt);

		Visitor visitor = new ProjectedProfitVisitor();
		int projectedProfit = 0;

		for (Item item : items) {

			projectedProfit = projectedProfit + item.accept(visitor);
		}

		System.out.println("Projected profit after applying discount:" + projectedProfit);

	}
}

I have created a book object , a phone object and a shirt object and set their name , original price, selling price and stock (quantity) details.

These are then added to a list.

The list is then iterated and each item calls the method visit() on a Visitor class. The return values are summed up.

That’s it . With few lines of code we have calculated our total projected profit.

Now let’s look at our item classes to see if there is new code added to handle this calculation:

package behavioural.visitor.pattern;

public class Book implements Item {

	private String name;

	private int quantity;

	private int originalPrice;

	private int sellingPrice;

	@Override
	public int accept(Visitor visitor) {

		return visitor.visit(this);
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getQuantity() {
		return quantity;
	}

	public void setQuantity(int quantity) {
		this.quantity = quantity;
	}

	public int getOriginalPrice() {
		return originalPrice;
	}

	public void setOriginalPrice(int originalPrice) {
		this.originalPrice = originalPrice;
	}

	public int getSellingPrice() {
		return sellingPrice;
	}

	public void setSellingPrice(int sellingPrice) {
		this.sellingPrice = sellingPrice;
	}

	@Override
	public String toString() {
		return "Book [name=" + name + ", quantity=" + quantity + ", originalPrice=" + originalPrice + ", sellingPrice="
				+ sellingPrice + "]";
	}

}

It is a simple Book domain class with getters and setters. There is only method of interest here.

The method visit(). It accepts a new Visitor. This visitor can visit this book and perform any behaviour it wants. Look how the visitor instance then calls its own method by taking a reference to the book:

return visitor.visit(this);

The above line of code is the core of the visitor pattern.

A visitor class visits an object , takes its instance and then calls its own method!

The data and methods of Book class are not disturbed. Only a new method (accept()) is added which doesn’t access any of book’s data or methods.

Similar classes are created for Phone and Shirt.

They all share the same interface:

package behavioural.visitor.pattern;

public interface Item {
	
	int getSellingPrice();
	
	int getOriginalPrice();
	
	int getQuantity();
	
	String getName();

	int accept(Visitor visitor);

}

The method accept(Visitor visitor) is of significance here.

Here is the visitor interface which our custom visitor is going to implement:

package behavioural.visitor.pattern;

public interface Visitor {
	
	
	int visit(Book book);
	
	int visit(Phone phone);
	
	int visit(Shirt shirt);

}

And here is the implementation :

package behavioural.visitor.pattern;

public class ProjectedProfitVisitor implements Visitor {

	double projectedBookSales = .8; // 80 percent

	double projectedShirtSales = .9; // 90 percent

	double projectedPhoneSales = .75; // 75 percent

	public int visit(Book book) {

		int bookQuantity = (int) (book.getQuantity() * projectedBookSales);

		int totalSellingPrice = (int) ((book.getSellingPrice() - 0.4 * book.getSellingPrice()) * bookQuantity);

		int totalPurchasePrice = book.getOriginalPrice() * bookQuantity;

		int profit = totalSellingPrice - totalPurchasePrice;

		System.out.println();

		System.out.println("Calculating projected profit for books ");

		System.out.println("Projected sales:" + projectedBookSales * 100 + "%");

		System.out.println("Total books expected to be sold" + bookQuantity);

		System.out.println("40 percent discount selected");

		System.out.println("Total purchase price" + totalPurchasePrice);

		System.out.println("Total sales price" + totalSellingPrice);

		System.out.println("Projected Profit" + profit);

		return profit;

	}

	public int visit(Phone phone) {

		int phoneQuantity = (int) (phone.getQuantity() * projectedPhoneSales);
		int totalSellingPrice = (int) ((phone.getSellingPrice() - 0.2 * phone.getSellingPrice()) * phoneQuantity);

		int totalPurchasePrice = phone.getOriginalPrice() * phoneQuantity;

		int profit = totalSellingPrice - totalPurchasePrice;

		System.out.println();

		System.out.println("Calculating projected profit for phones ");

		System.out.println("Projected sales:" + projectedPhoneSales * 100 + "%");

		System.out.println("Total phones expected to be sold" + phoneQuantity);

		System.out.println("20 percent discount selected");

		System.out.println("Total purchase price" + totalPurchasePrice);

		System.out.println("Total sales price" + totalSellingPrice);

		System.out.println("Projected Profit" + profit);

		return profit;

	}

	public int visit(Shirt shirt) {

		int shirtQuantity = (int) (shirt.getQuantity() * projectedShirtSales);

		int totalSellingPrice = (int) ((shirt.getSellingPrice() - 0.5 * shirt.getSellingPrice()) * shirtQuantity);

		int totalPurchasePrice = shirt.getOriginalPrice() * shirtQuantity;

		int profit = totalSellingPrice - totalPurchasePrice;

		System.out.println();

		System.out.println("Calculating projected profit for shirts ");

		System.out.println("Projected sales:" + projectedShirtSales * 100 + "%");

		System.out.println("Total shirts expected to be sold" + shirtQuantity);

		System.out.println("50 percent discount selected");

		System.out.println("Total purchase price" + totalPurchasePrice);

		System.out.println("Total sales price" + totalSellingPrice);

		System.out.println("Projected Profit" + profit);

		return profit;

	}
}

All the logic happens inside our custom visitor ProjectedProfitVisitor.

If you remember , in client code we passed an instance of this visitor to every item through accept() method. And inside the accept() method , the visit() method of ProjectedProfitVisitor class was called. Based on the argument type , specific visit() method was called.

Thus for calculating the projected profit of book item , the below code in the above class got invoked:

	public int visit(Book book) {

		int bookQuantity = (int) (book.getQuantity() * projectedBookSales);

		int totalSellingPrice = (int) ((book.getSellingPrice() - 0.4 * book.getSellingPrice()) * bookQuantity);

		int totalPurchasePrice = book.getOriginalPrice() * bookQuantity;

		int profit = totalSellingPrice - totalPurchasePrice;

		System.out.println();

		System.out.println("Calculating projected profit for books ");

		System.out.println("Projected sales:" + projectedBookSales * 100 + "%");

		System.out.println("Total books expected to be sold" + bookQuantity);

		System.out.println("40 percent discount selected");

		System.out.println("Total purchase price" + totalPurchasePrice);

		System.out.println("Total sales price" + totalSellingPrice);

		System.out.println("Projected Profit" + profit);

		return profit;

	}

Let’s see what happens here:

We have hard coded the projected sales percentage (80 percent of the books ) and the discount percentage (40 percent) inside the visitor class.

The visit() method then calculates the projected profit using the above values and original price , selling price and quantity values.

It returns the projected profit for books.

This is returned to the client code.

Similarly projected profit for phones and shirts are also returned.

We sum up all these to get the total projected profit.

Here is part of the client code again to highlight this :

Visitor visitor = new ProjectedProfitVisitor();
		int projectedProfit = 0;

		for (Item item : items) {

			projectedProfit = projectedProfit + item.accept(visitor);
		}

Below is the output of the client code:

Calculating projected profit for books
Projected sales:80.0%
Total books expected to be sold32
40 percent discount selected
Total purchase price16000
Total sales price19200
Projected Profit3200
Calculating projected profit for phones
Projected sales:75.0%
Total phones expected to be sold37
20 percent discount selected
Total purchase price1480000
Total sales price1924000
Projected Profit444000
Calculating projected profit for shirts
Projected sales:90.0%
Total shirts expected to be sold90
50 percent discount selected
Total purchase price18000
Total sales price22500
Projected Profit4500
Projected profit after applying discount:451700

That’s it.

We didn’t disturb existing code much.

Of course we added an accept() method to every item class, but it is much better than adding a new method inside every Item class.

By introducing a visitor we encapsulated our logic for calculating projected profit inside a separate visitor class.

We can remove it later easily if we don’t want, without breaking the code.

Also we can introduce new visitors without adding any more code to the domain classes. The new visitor should just implement all methods in the interface “Visitor” .

Thus Visitor helps to add new logic to existing group of objects without modifying it.

When I started researching Visitor pattern for this post , it almost looked like Strategy pattern that I found it very hard to distinguish them initially.

But there is a big difference which I found out later on.

Strategy pattern is not used to apply new logic on existing code. It is used to implement existing behavior in different ways / different strategies.

For example , an ecommerce shop can use different pricing strategies to sell its items.

Selling Items is an existing, core behavior of an ecommerce shop. Strategy pattern helps in adopting different strategies / algorithms to implement it.

Whereas Visitor pattern helps to add new behavior to existing objects. Like in our case the core logic of Book Sales is not implemented in Visitor pattern. An ad hoc request to calculate projected profit for books was implemented using it.

In one sentence – Strategy pattern is used to implement existing behavior.

Visitor pattern is used to add new behavior.

Quoting the Gang of Four :

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.

Here is the code:

https://github.com/vijaysrj/designPatternsGoF/tree/master/src/behavioural/interpreter/pattern


Posted

in

,

by

Comments

Leave a Reply

Discover more from The Full Stack Developer

Subscribe now to keep reading and get access to the full archive.

Continue reading