设计模式之责任链模式

296 阅读6分钟

责任链模式

介绍

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

定义

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

使用场景

  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. 设计思路是把各个业务逻辑判断封装到不同职责类,且携带下一个职责的对应引用,但不像状态模式那样需要明确知道这个引用指向谁,而是在环境类设置链接方式或者过程
    3. 使用时,向链的第一个子类的执行方法传递参数就可以。客户端去通过环境类调用责任链,全自动运转起来
  • 状态模式:

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

    1. 优点显而易见,可以对请求者和处理者关系解耦,提高代码灵活性。
    2. 最大的缺点是对链中请求处理者的遍历,处理处理者太多,遍历必然会影响性能,特别是在一些递归调用中。