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
Sometimes you can think of using existing connections or chains in an object. For example, if you are using composite pattern you already have a chain that can be used to implement this behavior.
2.3 Pitfalls
There is no guarantee provided in the pattern that a request will be handled. Request can traverse whole chain and fall off at the other end without ever being processed and we won't know it
It is easy to misconfigure the chain when we are connection successors. There is nothing in the pattern that will let us know of any such problems. Some handlers may be left unconnected to the chain.
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;
}
}