-
Chain of responsibilityModeling/DesignPattern 2020. 2. 29. 19:06
1. Overview
- We need to avoid coupling the code which sends a request to the code which handles that request.
- Typically the code which wants some request handled calls the exact method on an exact object to processing it, thus the tight coupling. Chain of responsibility solves this problem by giving more than one object, chance to process the request.
- We create objects which are chained together by one object knowing reference of the object which is next in the chain. We give requests to the first object in the chain if it can't handle that it simply passes the request down the chain.
2. Description
2.1 Implementation
- Handler must define a method to accept incoming request
- Handler can define a method to access successors in chains. If it's an abstract class then we can even maintain successor
- Next we implement handler in one or more concrete handlers. Concrete handler should check if it can handle the request. If not then it should pass a request to the next handler.
- We have to create our chain of objects next. We can do it in client. Typically in the real world, this job will be done by some framework or initialization code written by you
- Client needs to know only the first object in the chain. It'll pass on the request to this object.
2.2 Consideration
2.2.1 Implementation
- Prefer defining handler as an interface as it allows you to implement a chain of responsibility without worrying about single inheritance rule of Java
- Handlers can allow the request to propagate even if they handle the request. Servlet filter chains allow a request to flow to the next filter even if they perform some action on the request
- Chain can be described using XML or JSON as well so that you can add and remove handlers from chain without modifying code.
2.2.2 Design
3. Usage
- All servlet filters implement the javax.servlet.Filter interface whihc defines following doFilter method
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- Implementations will use FilterChain object to pass the request to next handler in the chain
4. Comparison with Command
Chain of Responsibility Command If handler can't handle the request it will pass it on to next handler With command there is no passing it on of request. Command handles the request itself There is no guarantee that the request will be handled by at least one handler With command pattern it is assumed that command will be executed and the request will be handled We don't track which handler handled the request and can't reverse the actions of handler Commands are trackable. We can store command instances in the same order as they execute and they are reversible in nature. 5. Example
//This represents a handler in chain of responsibility interface LeaveApprover { void processLeaveApplication(LeaveApplication application); String getApproverRole(); } //Represents a request in our chain of responsibility class LeaveApplication { public enum Type {Sick, PTO, LOP}; public enum Status {Pending, Approved, Rejeceted }; private Type type; private LocalDate from; private LocalDate to; private String processedBy; private Status status; public LeaveApplication(Type type, LocalDate from, LocalDate to) { this.type = type; this.from = from; this.to = to; this.status = Status.Pending; } public Type getType() { return type; } public LocalDate getFrom() { return from; } public LocalDate getTo() { return to; } public int getNoOfDays() { return Period.between(from, to).getDays(); } public String getProcessedBy() { return processedBy; } public Status getStatus() { return status; } public void approve(String approverName) { this.status = Status.Approved; this.processedBy = approverName; } public void reject(String approverName) { this.status = Status.Rejeceted; this.processedBy = approverName; } public static Builder getBuilder() { return new Builder(); } @Override public String toString() { return type + " leave for "+getNoOfDays()+" day(s) "+status + " by "+processedBy; } public static class Builder { private Type type; private LocalDate from; private LocalDate to; private LeaveApplication application; private Builder() { } public Builder withType(Type type) { this.type = type; return this; } public Builder from(LocalDate from) { this.from = from; return this; } public Builder to(LocalDate to) { this.to = to; return this; } public LeaveApplication build() { this.application = new LeaveApplication(type, from, to); return this.application; } public LeaveApplication getApplication() { return application; } } } //Abstract handler abstract class Employee implements LeaveApprover{ private String role; private LeaveApprover successor; public Employee(String role, LeaveApprover successor) { this.role = role; this.successor = successor; } @Override public void processLeaveApplication(LeaveApplication application) { // child class not handle application and there is successor if(!processRequest(application) && successor != null) { // handle request to next successor successor.processLeaveApplication(application); } } protected abstract boolean processRequest(LeaveApplication application); @Override public String getApproverRole() { return role; } } //A concrete handler class ProjectLead extends Employee{ public ProjectLead(LeaveApprover successor) { super("Project Leader", successor); } @Override protected boolean processRequest(LeaveApplication application) { // type is sick leave and duration is less than or equal to 2 days if(application.getType() == LeaveApplication.Type.Sick) { if(application.getNoOfDays() <= 2) { application.approve(getApproverRole()); return true; } } return false; } } class Manager extends Employee { public Manager(LeaveApprover nextApprover) { super("Manager", nextApprover); } @Override protected boolean processRequest(LeaveApplication application) { switch (application.getType()) { case Sick: application.approve(getApproverRole()); return true; case PTO: if(application.getNoOfDays() <= 5) { application.approve(getApproverRole()); return true; } } return false; } } //A concrete handler class Director extends Employee { public Director(LeaveApprover nextApprover) { super("Director", nextApprover); } @Override protected boolean processRequest(LeaveApplication application) { if(application.getType() == LeaveApplication.Type.PTO) { application.approve(getApproverRole()); return true; } return false; } } public class Client { public static void main(String[] args) { LeaveApplication application = LeaveApplication.getBuilder().withType(LeaveApplication.Type.Sick) .from(LocalDate.now()).to(LocalDate.of(2020, 3,14)).build(); System.out.println(application); System.out.println("****************************************************************"); LeaveApprover approver = createChain(); approver.processLeaveApplication(application); System.out.println(application); } private static LeaveApprover createChain() { Director director = new Director(null); Manager manager = new Manager(director); ProjectLead lead = new ProjectLead(manager); return lead; } }
6. Reference