设计模式之责任链模式

责任链模式

介绍

因为链式结构具有很好的灵活性,将其应用于编程领域:将每个节点当作一个对象,没个对象有不同的处理逻辑,将一个从请求从链式的首端发出,沿着链的路径依次传递给每一个节点对象,直至有对象处理这个请求为止

定义

是很多对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有对象处理它为之。

使用场景

  1. 多个对象可以处理同一个请求,但具体由哪个对象处理则在运行时动态决定
  2. 在请求处理者不明确的情况下,向多个对象中的一个提交一个请求
  3. 需要动态指定一组对象处理请求

通用模版

  • 抽象处理者Handler

    public abstract class Handler {
    
        //下一个节点的处理者
        protected Handler successor;
    
        //请求处理
        public abstract void handleRequest(String condition);
    
    }
    

    抽象处理者需要子类继承实现其具体处理的逻辑,可以借由父类的successor赋值当前节点下一个节点处理者

  • 具体处理者1

    public class ConcreteHandler1 extends Handler {
        @Override
        public void handleRequest(String condition) {
            if (condition.equals("ConcreteHandler1")){
                System.out.println("ConcreteHandler1 handled");
                return;
            }else {
                if (successor!=null)
                    successor.handleRequest(condition);
            }
        }
    }
    

    具体处理者继承父类处理者,实现具体处理的逻辑。如果不是自己能处理的则转交下一个节点继续处理,对于下一节点的设置需要在Client中指明。

  • 具体处理者2

    public class ConcreteHandler2 extends Handler {
        @Override
        public void handleRequest(String condition) {
            if (condition.equals("ConcreteHandler2")){
                System.out.println("ConcreteHandler2 handled");
                return;
            }else {
                if (successor!=null){
                    successor.handleRequest(condition);
                }
            }
        }
    }
    

    具体处理者2得到节点1发来消息继续处理(节点一指明节点2为ConcreteHandler2),如果符合自己处理的情况直接处理,否则继续向下一个节点传递。

  • Client客户端

    public class Client {
        public static void main(String args[]){
            //    实例两个具体处理者
            ConcreteHandler1 concreteHandler1 = new ConcreteHandler1();
            ConcreteHandler2 concreteHandler2 = new ConcreteHandler2();
    
            // 指明调用者下一个节点
            concreteHandler1.successor = concreteHandler2;
            concreteHandler2.successor = concreteHandler1;
    
            // 链首开始调用
            concreteHandler1.handleRequest("ConcreteHandler2");
        }
    }
    

    客户端内首先实例两个具体的处理者,对处理者之间指明下一个节点,方便调用时可以向下传递。这里可以看出两者互为下一个节点。最后从链首开始调用“ConcreteHandler2”。

  • LOG如下

    ConcreteHandler2 handled
    

ConcreteHandler1直接调用参数名“ConcreteHandler2”,内部逻辑判断不能直接处理,转交下一节点ConcreteHandler2来调用,ConcreteHandler2能够处理此逻辑,直接打印完成本次链式调用。

升级模版

由于责任链中的请求和对应的处理规则是不尽相同的,在这种情况下封装请求,同时对请求的处理规则也进行封装,代码如下:

  • 抽象出处理者

    public abstract class AbstractHandler {

    //下一节点的处理者对象
    protected AbstractHandler nextHandler;
    
    //请求处理
    public final void handleRequest(AbstractRequest request){
        if (request.getRequestLevel() == getHandleLevel()){
            handle(request);
        }else {
            if (nextHandler!=null){
                nextHandler.handleRequest(request);
            }else
                System.out.println("All of handler can not handle the request!");
        }
    
    }
    
    //由子类实现,具体处理方式
    public abstract void handle(AbstractRequest request);
    
    //由子类实现,获取处理者对象级别
    public abstract int getHandleLevel();
    

    }
    同上抽象处理者相似,除了成员变量下一节点,对处理的逻辑进行一定的的封装。首先检测当前处理级别是否对应,对应直接由虚拟方法子类具体实现处理,否则判断节点并交予下一节点处理,不能处理便直接打印处理。
    同时处理中使用了封装的AbstractRequest请求,下面介绍。

  • 具体处理者1,2,3

    public class Handler1 extends AbstractHandler {
        @Override
        public void handle(AbstractRequest request) {
            System.out.println("Handler1 handle the request: "+ request.getRequestLevel());
        }
    
        @Override
        public int getHandleLevel() {
            return 1;
        }
    }
    

    具体处理者直接处理哭啼逻辑,父类已经进行了相关的等级判断,这是共有的逻辑可以封装在父类中,同时需要返回当前处理的等级,以满足父类判断的需要。

  • 抽象请求者

    public abstract class AbstractRequest {
    
        //处理对象
        private Object obj;
    
        public AbstractRequest(Object obj) {
            this.obj = obj;
        }
    
        public Object getContext(){
            return obj;
        }
    
        public abstract int getRequestLevel();
    
    }
    

    抽象请求者封装抽象处理者需要的等级判断条件,需要子类实现具体请求的等级。

  • 具体请求者1,2,3

    public class Request1 extends AbstractRequest {
        public Request1(Object obj) {
            super(obj);
        }
    
        @Override
        public int getRequestLevel() {
            return 1;
        }
    }
    

    方法相对简单,直接返回当前对象对应的请求等级。

  • Client端实现

    public class Main {
    
        public static void main(String args[]) {
    
            //构造三个处理者对象
            Handler1 handler1 = new Handler1();
            Handler2 handler2 = new Handler2();
            Handler3 handler3 = new Handler3();
    
        //设置当前处理者对象的下一个处理者
        handler1.nextHandler = handler2;
        handler2.nextHandler = handler3;

        //构造3个请求者
        Request1 request1 = new Request1("Request1");
        Request2 request2 = new Request2("Request2");
        Request3 request3 = new Request3("Request3");

        //从链式的首端发起请求
        handler1.handleRequest(request1);
        handler1.handleRequest(request2);
        handler1.handleRequest(request3);

    }
}

Client端首先构建三个处理者,并设置对应的节点关系。之后实例三个具体的请求者。链式调用从链首开始,调用Handlelr1处理请求,让其依次处理请求1,2,3,对于请求1,自己可以直接处理,对于请求2,3,处理者1不能直接处理,只能向下节点传递,有下面节点处理。

  • LOG分析

    Handler1 handle the request: 1
    Handler2 handle the requset: 2
    Handler3 handle the request: 3
    

从log我们可以看出:

  1. 具体处理者1可以直接处理请求1;
  2. 对于请求2,虽然使用处理者1来调用,但是可以通过链式传递到达节点2,由处理者2处理;
  3. 对于请求3,处理者1不满足处理条件,通过链转下一节点到处理者2,处理者2仍然不满足处理条件,再通过链转交节点3,处理者3满足条件直接处理。

总结

  • 责任链模式与状态者模式有相似之处,但也有区别:

    1. 职责链模式中的各个对象并不指定其下一个处理的对象到底是谁,只有在客户端才设定某个类型的链条,请求发出后穿越链条,直到被某个职责类处理或者链条结束。设计思路是把各个业务逻辑判断封装到不同职责类,且携带下一个职责的对应引用,但不像状态模式那样需要明确知道这个引用指向谁,而是在环境类设置链接方式或者过程。使用时,向链的第一个子类的执行方法传递参数就可以。客户端去通过环境类调用责任链,全自动运转起来。

    2. 状态模式是让各个状态对象自己知道其下一个处理的对象是谁,即在编译时便设定。设计思路是把逻辑判断转移到各个State类的内部实现(相当于If,else If),执行时客户端通过调用环境—Context类的方法来间接执行状态类的行为,客户端不直接和状态交互。

  • 优缺点

    1. 优点显而易见,可以对请求者和处理者关系解耦,提高代码灵活性。
    2. 最大的缺点是对链中请求处理者的遍历,处理处理者太多,遍历必然会影响性能,特别是在一些递归调用中。
坚持原创技术分享,您的支持将鼓励我继续创作!