The problem: For complex business logic with lots of conditions, we could certainly use one giant class with lots of ifs. But by doing this, the class would be highly coupled and with no cohesion. Consider using the Chain of Responsibility Pattern when you have business logic to implement that is dependent on several conditions.
Pros: Using this Pattern you can encapsulate your logic and you can easily create new features using different conditions.
Cons: If the chain gets too large, we can have performance problems. Be careful when using this Pattern.
The diagram:
The source code:
https://github.com/rafadelnero/design-patterns-saga.git
1 – The Handler class: This class controls all the flows of the Chain of Responsibility Pattern.
public void setSucessor(Handler successor) – It sets the next chain to handle the next condition. We must create a chain based on all the classes that extend Handler.
public abstract Response handleRequest(Request request) – All the chain classes must implement this method. All the business logic will be executed inside this method.
public abstract class Handler { protected Handler successor; public void setSucessor(Handler successor) { this.successor = successor; } public abstract Response handleRequest(Request request); }
2 – The DiscountHandler class: In this class, we will configure the Chain of the classes that can handle the logic. Basically, we will instantiate all the classes and set them in the setSuccessor(Handler handler) method. Remember that this Chain must be executed in the correct order as the following:
public class DiscountHandler { public Response applyDiscount(Request request) { Handler noDiscount = new NoDiscount(); Handler basic = new BasicDiscount(); Handler moderate = new ModerateDiscount(); Handler vip = new VipDiscount(); noDiscount.setSucessor(basic); basic.setSucessor(moderate); moderate.setSucessor(vip); return noDiscount.handleRequest(request); } }
3 – The Chain: Now we have the classes that will create the chain. We must instantiate them from the root. In the case of this chain, they must be executed in the correct order.
All the classes must extend Handler to be part of the Chain.
When extending the Handler class we must implement the handleRequest abstract method. For example, if the request corresponds to the BasicDiscount condition, the logic from this class will be executed or else the next object in the Chain will handle this logic and so forth.
The classes from the chain are:
public class NoDiscount extends Handler { @Override public Response handleRequest(Request request) { if (request.getCustomerSalesAmount() .compareTo(BasicDiscount.minimalValue) < 0) { return new Response(DiscountType.NO_DISCOUNT); } else { return successor.handleRequest(request); } } } public class BasicDiscount extends Handler { public static BigDecimal minimalValue = new BigDecimal("10000"); @Override public Response handleRequest(Request request) { if (request.getCustomerSalesAmount() .compareTo(ModerateDiscount.minimalValue) < 0) { System.out.println("Execute some business logic here"); return new Response(DiscountType.BASIC); } else { return successor.handleRequest(request); } } } public class ModerateDiscount extends Handler { public static BigDecimal minimalValue = new BigDecimal("50000"); @Override public Response handleRequest(Request request) { if (request.getCustomerSalesAmount() .compareTo(VipDiscount.minimalValue) < 0) { return new Response(DiscountType.MODERATE); } else { return successor.handleRequest(request); } } } public class VipDiscount extends Handler { public static BigDecimal minimalValue = new BigDecimal("100000"); @Override public Response handleRequest(Request request) { if (request.getCustomerSalesAmount() .compareTo(minimalValue) >= 0) { System.out.println("Execute some business logic here"); return new Response(DiscountType.VIP); } throw new IllegalArgumentException("Invalid argument."); } }
4 – The Unitary Tests: Finally we will see the Pattern working! We are going to test if the request value corresponds to the correct condition. The test will be done for all the Chain classes.
public class ChainOfResponsibilityTest { @Test public void verifyIfBasicDiscountWasAppliedTest() { Response response = new DiscountHandler() .applyDiscount(new Request(new BigDecimal("20000"))); Assert.assertEquals(response .getDiscountType(), DiscountType.BASIC); } @Test public void verifyIfModerateDiscountWasAppliedTest() { Response response = new DiscountHandler() .applyDiscount(new Request(new BigDecimal("50000"))); Assert.assertEquals(response .getDiscountType(), DiscountType.MODERATE); } @Test public void verifyIfVipDiscountWasAppliedTest() { Response response = new DiscountHandler() .applyDiscount(new Request(new BigDecimal("100000"))); Assert.assertEquals(response .getDiscountType(), DiscountType.VIP); } @Test public void verifyIfNoDiscountWasAppliedTest() { Response response = new DiscountHandler() .applyDiscount(new Request(new BigDecimal("5000"))); Assert.assertEquals(response .getDiscountType(), DiscountType.NO_DISCOUNT); } }
Summary of actions:
1 – Created the Handler class.
2 – Extended the Handler class by the Chain classes.
3 – Implemented the handleRequest(Request request) method in the Chain classes.
4 – Created the Chain using the setSuccessor(Handler handler) method.
5 – Invoked the handleRequest method from the Chain root.
To practice the Chain of Responsibility Pattern you can create another DiscountType, for example, the SpecialDiscount and create another test method to make sure it works! Try to use TDD (Test Driven Development). Start the development from the test. Remember to use the IDE’s shortcuts for faster coding!
Thank You
You are welcome, Dennis! Stay tuned for more every week! See you!
You are welcome Dennis! Remember, master all the Design Patterns and take your programming skills to the next level!