java设计模式-责任链模式

今天给大家介绍责任链模式(Chain of Responsibility Pattern),责任链模式是属于设计模式中的行为型模式。巧用设计模式可以让我们的代码解耦,提高可拓展性和维护性,但一定不要为了使用而使用。

一、定义

  • 原文:Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

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

  • 维基百科定义:In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain.

在面向对象的设计中,责任链模式是一种由命令对象的源和一系列处理对象组成的设计模式。每个处理对象都包含定义其可以处理的命令对象类型的逻辑。其余的将传递到链中的下一个处理对象。

  • 简要总结:责任链模式是将链中的每一个节点看做一个对象,且内部维护下一个节点对象的引用,形成一个单链表,当收到一个请求时,会沿着链的路径一次传递给每一个节点对象,当然每个节点对象可以选择继续传递或者提前退出,直到最后一个节点对象。

二、应用

1. 应用场景

责任链模式的使用也是比较广泛的,比如我们OA系统的审批流,或者某一个特殊的业务需要经过几个比较复杂的流程处理等,比如一个添加业务,假设我们先要进行数据校验,然后进行入库操作,最后要通知其他系统做特殊处理,就可以做成一个责任链模式,代码整体耦合性降低,易于维护。

2. 角色
  • Handler(抽象处理者)

    定义请求处理方法,以及维护下一个处理者的引用

  • ConcreteHandler(具体处理者)

    处理请求的具体角色

3. 类图

责任链模式的类图比较简单,所有处理节点都继承同一个父类,这个父类可以是抽象类或者具体实现类,类中维护下一个处理节点的引用以及节点的处理请求方法(这个方法可以是抽象方法,也可以是具体实现,当然还可以利用钩子方法实现对子类方法的调用),调用者不关注具体节点的实现,只调用节点的处理方法即可。

image
4. 举例说明

假设现在有一个请假流程,小于三天组长审批即可,大于3天同时需要CTO审批,大于10天同时需要CEO审批。组长和CTO以及CEO都有权利审批通过或者驳回,如果审批通过,根据请假天数判断是否需要流转到下一节点,如果驳回请求,则直接返回。

首先看下整体类图:

image

首先创建一个父类处理器,

public class RequestHandler {

  private RequestHandler next; // 下一个节点的引用

  public RequestHandler(RequestHandler next) {
    this.next = next;
  }

  public void handleRequest(Request request) {
    if (next != null) { // 如果下一个节点不为空,则继续调用下一个节点的处理方法
      next.handleRequest(request);
    }
  }

}

创建三个节点处理对象,分别是组长处理、CTO处理、CEO处理:

public class LeaderHandler extends RequestHandler {

  public LeaderHandler(RequestHandler next) {
    super(next);
  }

  @Override
  public void handleRequest(Request request) {
    if (request.getEmployeeName().equals("张三")) {
      System.out.println("组长驳回【 " + request.getEmployeeName() + " 】请假");
      request.setReject();
      return;
    }
    System.out.println("组长通过【 " + request.getEmployeeName() + " 】请假");
    if (request.getDays() > 3) {
      super.handleRequest(request);
    } else {
      request.setPass();
    }
  }

}
public class CTOHandler extends RequestHandler {

  public CTOHandler(RequestHandler next) {
    super(next);
  }

  @Override
  public void handleRequest(Request request) {
   if (request.getEmployeeName().equals("李四")) {
      System.out.println("CTO驳回【 " + request.getEmployeeName() + " 】请假");
      request.setReject();
      return;
    }
    System.out.println("CTO通过【 " + request.getEmployeeName() + " 】请假");
    if (request.getDays() > 10) {
      super.handleRequest(request);
    } else {
      request.setPass();
    }

  }

}
public class CEOHandler extends RequestHandler {

  public CEOHandler(RequestHandler next) {
    super(next);
  }

  @Override
  public void handleRequest(Request request) {
    if (request.getEmployeeName().equals("王五")) {
      System.out.println("CEO驳回【 " + request.getEmployeeName() + " 】请假");
      request.setReject();
      return;
    }
    System.out.println("CEO通过【 " + request.getEmployeeName() + " 】请假");
    request.setPass();
  }

}

然后创建一个请假处理对象:

public class Request {

  private String employeeName; // 员工姓名

  private int days; // 请假天数

  private boolean handleResult; // 请假是否通过

  public Request(String employeeName, int days) {
    this.employeeName = employeeName;
    this.days = days;
  }

  public String getEmployeeName() {
    return employeeName;
  }

  public void setEmployeeName(String employeeName) {
    this.employeeName = employeeName;
  }

  public int getDays() {
    return days;
  }

  public void setDays(int days) {
    this.days = days;
  }

  public void setPass() {
    this.handleResult = true;
  }

  public void setReject() {
    this.handleResult = false;
  }

  public boolean getHandleResult() {
    return this.handleResult;
  }
}

建立一个测试类:

public class ChainTest {

  public static void main(String[] args) {
    RequestHandler chain = new LeaderHandler(new CTOHandler(new CEOHandler(null)));

    Request zhangsan = new Request("张三", 3);
    chain.handleRequest(zhangsan);
    System.out.println("张三请假结果:" + zhangsan.getHandleResult());
    System.out.println("-------------------");

    Request lisi = new Request("李四", 5);
    chain.handleRequest(lisi);
    System.out.println("李四请假结果:" + lisi.getHandleResult());
    System.out.println("-------------------");

    Request wangwu = new Request("王五", 15);
    chain.handleRequest(wangwu);
    System.out.println("王五请假结果:" + wangwu.getHandleResult());
    System.out.println("-------------------");

    Request oldZhao = new Request("老赵", 15);
    chain.handleRequest(oldZhao);
    System.out.println("老赵请假结果:" + oldZhao.getHandleResult());
  }

}

打印结果如下:

组长驳回【 张三 】请假
张三请假结果:false
-------------------
组长通过【 李四 】请假
CTO驳回【 李四 】请假
李四请假结果:false
-------------------
组长通过【 王五 】请假
CTO通过【 王五 】请假
CEO驳回【 王五 】请假
王五请假结果:false
-------------------
组长通过【 老赵 】请假
CTO通过【 老赵 】请假
CEO通过【 老赵 】请假
老赵请假结果:true

我们可以看到输入不同的参数,所走的业务流程不同,有的只经过一个处理器,有的会走完所有的处理器。

三、和建造者模式的结合使用

上面的示例中,在父类维护next节点的引用我们是通过构造器来维护的,所以我们在初始化处理链的时候是通过下面代码实现的

RequestHandler chain = new LeaderHandler(new CTOHandler(new CEOHandler(null)));

可以看到代码整体显得比较臃肿,如果我们有六七个或者更多处理节点的时候,易读性会非常差,
而大多数情况下一般都是通过父类提供一个setNextHandler(RequestHandler handler)来维护下一个节点,如果用此方法来初始化处理链的话,代码如下:

LeaderHandler leader = new LeaderHandler();
CTOHandler cto = new CTOHandler();
CEOHandler ceo = new CEOHandler();
leader.setNextHandler(cto);
cto.setNextHandler(ceo);

我们可以看到需要不断的给当前节点设置下一个节点,易读性和维护性也是比较差的,至此我们想到了利用建造者模式和责任链模式的结合使用,来让此处的设计变的解耦又好维护。

我们在RequestHandler类中维护一个内部类,内部类中使用单向链表来维护处理节点的顺序,再通过addHandler方法添加所有处理链,在调用的时候提供一个build方法获取头结点,然后通过头节点的next属性获取后续所有的节点,这样只要我们在初始化处理链的时候按顺序添加处理结点,就不用在设置next结点,代码的可读性也提升了很多。

public class RequestHandler {

  private RequestHandler next;

  public void setNextHandler(RequestHandler next) {
    this.next = next;
  }

  public void handleRequest(Request request) {
    if (next != null) {
      next.handleRequest(request);
    }
  }

  public static class Builder {

    private RequestHandler head;

    private RequestHandler tail;

    public Builder addHandler(RequestHandler requestHandler) {
      if (head == null) {
        this.head = this.tail = requestHandler;
      } else {
        this.tail.next = requestHandler;
        this.tail = requestHandler;
      }
      return this;
    }

    public RequestHandler build() {
      return this.head;
    }

  }

}

调用的时候通过如下代码即可:

public class ChainTest {

  public static void main(String[] args) {

    RequestHandler.Builder builder = new RequestHandler.Builder();
    builder.addHandler(new LeaderHandler())
        .addHandler(new CTOHandler())
        .addHandler(new CEOHandler());
    RequestHandler handler = builder.build();

    Request zhangsan = new Request("张三", 3);
    handler.handleRequest(zhangsan);
    System.out.println("张三请假结果:" + zhangsan.getHandleResult());
    System.out.println("-------------------");

    Request lisi = new Request("李四", 5);
    handler.handleRequest(lisi);
    System.out.println("李四请假结果:" + lisi.getHandleResult());
    System.out.println("-------------------");

    Request wangwu = new Request("王五", 15);
    handler.handleRequest(wangwu);
    System.out.println("王五请假结果:" + wangwu.getHandleResult());
    System.out.println("-------------------");

    Request oldZhao = new Request("老赵", 15);
    handler.handleRequest(oldZhao);
    System.out.println("老赵请假结果:" + oldZhao.getHandleResult());
  }

}

四、在源码中的体现

public void log(LogRecord record) {
  if (!isLoggable(record.getLevel())) {
    return;
  }
  Filter theFilter = config.filter;
  if (theFilter != null && !theFilter.isLoggable(record)) {
    return;
  }

  // Post the LogRecord to all our Handlers, and then to
  // our parents' handlers, all the way up the tree.

  Logger logger = this;
  while (logger != null) {
    final Handler[] loggerHandlers = isSystemLogger
      ? logger.accessCheckedHandlers()
      : logger.getHandlers();

    for (Handler handler : loggerHandlers) {
      handler.publish(record);
    }

    final boolean useParentHdls = isSystemLogger
      ? logger.config.useParentHandlers
      : logger.getUseParentHandlers();

    if (!useParentHdls) {
      break;
    }

    logger = isSystemLogger ? logger.parent : logger.getParent();
  }
}

此处是把所有的handler都维护在一个数组中,然后通过循环handler调用next的处理结点

public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
  Assert.notNull(request, "Request must not be null");
  Assert.notNull(response, "Response must not be null");
  Assert.state(this.request == null, "This FilterChain has already been called!");
  if (this.iterator == null) {
    this.iterator = this.filters.iterator();
  }

  if (this.iterator.hasNext()) {
    Filter nextFilter = (Filter)this.iterator.next();
    nextFilter.doFilter(request, response, this);
  }

  this.request = request;
  this.response = response;
}

这里是将所有的Filter放到list中,然后通过调用doFilter()方法时循环迭代list,list中的Filter也会顺序执行。

五、优缺点

优点:
  • 降低代码的耦合度,将请求与处理解耦
  • 节点对象只需关注自己要处理的业务或者特定的请求即可,对于不想处理的可以直接传递给下一个节点
  • 链路结构灵活,可以通过改变链路结构动态的新增或者删减责任
  • 可以实时新增链路节点,符合开闭原则
缺点:
  • 责任链太长或者处理时间过长,会影响整体性能
  • 整体调用有点类似递归,排查问题较为费力
  • 代码拆分的小类会比较多
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354