Refactor to Visitors

1_1Kh8E9rSH_10dgTV1gXFSQ

Building modular and maintainable software is hard, and expertise is not enough to implement it on its own. The problem domain is better understood and edge cases are popping up gradually over time and refactoring becomes inevitable.

Image: https://www.instagram.com/p/BI-sLeCg10C/ Quote from The Pragmatic Programmer Book

1_wudbiQjVCm5Sf5yG0bjsDQ

Objects in your application can become quite bloated while the product makes its way to the MVP. This is called “technical debt”. It’s okay to have many technical debts during the prototyping phase because it’s almost impossible to avoid due to the obvious reasons mentioned above.

There are several ways to pay the technical debt in your objects. One of them is to utilize visitors. A visitor is a popular design pattern which aims to have more clear outlines of the bounded context of the domain by extracting and encapsulating business logic to different modules.

In this post, I will demonstrate how to refactor a business logic outside of the main object by utilizing a visitor pattern. I decided to use a basic shopping cart example. The initial ShoppingCart class below.

Some method implementations are omitted to simplify the code.

public class ShoppingCart {
ArrayList cartItems = new ArrayList();
public void add(ShoppingCartItem cartItem) {
// add item to cart
}
public Double calculateTotalPrice() {
// calculate and return total price
}
public Double calculateTotalTax() {
// calculate and return total tax
}
}

As you can see, there are two methods named calculateTotalPrice and calculateTotalTax which does the exact thing that they are named. There are a couple of problems here.

ShoppingCart class must be altered in order to add different calculations or features. This is a clear violation of the open/closed principle. Calculation implementations are tightly coupled to ShoppingCart class. They are implicit dependencies and cannot be reused.

I will start by removing the corresponding two methods, which will be converted to visitors later. In order for visitors to do their job, ShoppingCart must accept visitors. So let’s add an accept method and two variables to hold calculated total price and total tax along with the getters.

public class ShoppingCart {
ArrayList cartItems = new ArrayList();

Double totalPrice = 0.0;
Double totalTax = 0.0;
public void add(ShoppingCartItem cartItem) {
// add item to cart
}
public void accept(ShoppingCartVisitorInterface shoppingCartVisitor) {
shoppingCartVisitor.visit(this);
}
public Double getTotalPrice() {
return totalPrice;
}
public Double getTotalTax() {
return totalTax;
}
}

Notice the ShoppingCartVisitorInterface. We are not letting anyone visit ShoppingCart, but instances of ShoppingCartVisitorInterface.

public interface ShoppingCartVisitorInterface {
void visit(ShoppingCart shoppingCart);
}

Now it’s ready to accept visitors. So we need visitors. There should be two visitors for calculateTotalPrice and calculateTotalTax respectively. Let’s move on and create them.

public class PriceCalculatorVisitor implements ShoppingCartVisitorInterface {
@Override
public void visit(ShoppingCart shoppingCart) {
for (ShoppingCartItem cartItem : shoppingCart.cartItems
) {
shoppingCart.totalPrice += cartItem.quantity * cartItem.price;
}
}
}
public class TaxCalculatorVisitor implements ShoppingCartVisitorInterface {
@Override
public void visit(ShoppingCart shoppingCart) {
for (ShoppingCartItem cartItem : shoppingCart.cartItems
) {
shoppingCart.totalTax += cartItem.quantity * cartItem.price * 0.18;
}
}
}

Visitors can access internal variables of ShoppingCart because ShoppingCart presents itself when a visitor visits it via the accept method.

Let’s give it a spin:

public static void main(String[] args) {
ShoppingCart shoppingCart = new ShoppingCart();
ShoppingCartItem cartItem1 = new ShoppingCartItem(“Apple Iphone”, 1, 1200);
ShoppingCartItem cartItem2 = new ShoppingCartItem(“Sony Playstation”, 1, 650);
ShoppingCartItem cartItem3 = new ShoppingCartItem(“Dell XPS”, 1, 2150);
shoppingCart.add(cartItem1);
shoppingCart.add(cartItem2);
shoppingCart.add(cartItem3);
shoppingCart.accept(new TaxCalculatorVisitor());
shoppingCart.accept(new PriceCalculatorVisitor());
System.out.println(“Total tax: “ + shoppingCart.getTotalTax());
System.out.println(“Total price: “ + shoppingCart.getTotalPrice());
}

Output:

Total tax: 720.0 Total price: 4000.0

This is pretty much it. We have a class that accepts visitors and visitors which can execute related business logic on the target class. Some business logic decoupled nicely!

Take care~

by Tuncay Üner

Comments

There are no comments yet.

Leave a comment