ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Chain of responsibility
    Modeling/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

    • 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;
        }
    }

    6. Reference

     

    'Modeling > DesignPattern' 카테고리의 다른 글

    Memento  (0) 2020.03.01
    Composite  (0) 2020.02.29
    Visitor  (0) 2020.02.29
    Iterator  (0) 2020.02.29
    Command  (0) 2020.02.29

    댓글

Designed by Tistory.