Visitor Design Pattern real world example in Java
In this post we will see when to use visitor design pattern and the real life example of visitor design pattern in Java.
Scenario for understanding Visitor Design Pattern.
Consider a scenario below,
You own a small supermarket selling items of multiple category ranging from Electronics, Hardware, Plastic, Packaged food, Fresh fruit etc.
- Supermarket have return policy based on the item category.
- Supermarket provide discounts based on the item category.
- Supermarket have different shipping vendor based on item category. (say you purchased an item from e-commerce company like Amazon and now you want to return it. In some countries, they arrange a person to visit your place for picking the item. In some countries, they email you pre-paid shipping label from standard transportation companies like Canada post, UPS etc and you have to pack the item, stick the shipping label and handover to the shipping provider.)
too much explanation... :) lets look into how above scenario can be designed without using visitor design pattern and then we will see benefits of visitor pattern and how it helps in maintaining Open/Closed and Single Responsibility Principle.
For each different category we have to apply different rules in terms of discounts, return policy and shipping vendor.
Problem without Visitor Design Pattern.
We have multiple product category and each have the name, discount, return policy and shipping vendor. we would design something like below,
interface ProductCategory {
String getName();
int discount();
String shippingVendor();
int returnPolicy();
}
class Plastic implements ProductCategory {
@Override
public String getName() {
return "Plastic";
}
@Override
public int discount() {
return 5;
}
@Override
public String shippingVendor() {
return "UPS";
}
@Override
public int returnPolicy() {
return 30;
}
}
class Electronics implements ProductCategory {
@Override
public String getName() {
return "Electronics";
}
@Override
public int discount() {
return 10;
}
@Override
public String shippingVendor() {
return "Canada Post";
}
@Override
public int returnPolicy() {
return 60;
}
}
Now, Imagine you have to add one more rule say packaging type for each category. what we will do is modify the interface to add one more method,
interface ProductCategory {
String getName();
int discount();
String shippingVendor();
int returnPolicy();
String packingType(); //added
}
You have to modify all the implementation to have this packing type. which is against Open/Closed principle.
Also, responsibility like Discounts, return policy are segregated into each implementation and not at one place.
Lets see the sample implementation with Visitor Design pattern now,
Visitor Design Pattern.
With Visitor Design pattern, all such rules that can be applied to each product category is separated from category itself.
Example: Electronics category will not have any implementation details for discounts, return policy, shipping vendor etc as those are the rules that could be added/removed to each category and could change.
so what we would be doing is to allow Electronics category having a visitor called ProductCategoryVisitor (which could be of type DiscountVisitor, ShippingVendorVisitor, ReturnPolicyVisior etc).
package fresh.armaan;
public class VisitorDesignPattern {
public static void main(String[] args) {
ProductCategoryVisitor discountVisitor = new DiscountVisitor();
ProductCategoryVisitor returnPolicyVisitor = new ReturnPolicyVisitor();
//Plastic
ProductCategory plasticCategory = new Plastic();
System.out.println("Category Name : " + plasticCategory.getName());
System.out.println("Discounts on Plastic Category : " + plasticCategory.visit(discountVisitor) + "%");
System.out.println("Return Policy on Plastic Category : " + plasticCategory.visit(returnPolicyVisitor) + " days");
System.out.println("------------------");
//Electronics
ProductCategory electronicsCategory = new Electronics();
System.out.println("Category Name : " + electronicsCategory.getName());
System.out.println("Discounts on Electronics Category : " + electronicsCategory.visit(discountVisitor) + "%");
System.out.println("Return Policy on Electronics Category : " + electronicsCategory.visit(returnPolicyVisitor) + " days");
}
}
interface ProductCategory {
String getName();
String visit(ProductCategoryVisitor visitor);
}
class Plastic implements ProductCategory {
@Override
public String getName() {
return "Plastic";
}
@Override
public String visit(ProductCategoryVisitor visitor) {
return visitor.visit(this);
}
}
class Electronics implements ProductCategory {
@Override
public String getName() {
return "Electronics";
}
@Override
public String visit(ProductCategoryVisitor visitor) {
return visitor.visit(this);
}
}
interface ProductCategoryVisitor {
String visit(Electronics electronics);
String visit(Plastic plastic);
}
class DiscountVisitor implements ProductCategoryVisitor {
@Override
public String visit(Electronics electronics) {
return "10";
}
@Override
public String visit(Plastic plastic) {
return "5";
}
}
class ReturnPolicyVisitor implements ProductCategoryVisitor {
@Override
public String visit(Electronics electronics) {
return "60";
}
@Override
public String visit(Plastic plastic) {
return "30";
}
}
Output:
Category Name : Plastic
Discounts on Plastic Category : 5%
Return Policy on Plastic Category : 30 days
------------------
Category Name : Electronics
Discounts on Electronics Category : 10%
Return Policy on Electronics Category : 60 days
Use visitor pattern when you have a fairly stable class hierarchy (like in our example we have product category Electronics, Plastic etc which changes but not very frequently), but have changing requirements of what needs to be done with that hierarchy (in our example each product category have discounts, return policy, shipping vendor and so on and that could add/remove/change very often), in that case visitor pattern is helpful.
You may also like to see
Enjoy !!!!
If you find any issue in post or face any error while implementing, Please comment.