前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[设计模式] 责任链模式

[设计模式] 责任链模式

作者头像
呼延十
发布2019-06-26 16:49:46
5280
发布2019-06-26 16:49:46
举报
文章被收录于专栏:呼延呼延

1.介绍

使多个对象都有机会处理请求,从而避免请求的发送者与接受者之间的耦合关系. 将多个接受者连成一条链,沿着该链处理请求,直到请求被处理为止.

类图

2019-03-19-16-27-01
2019-03-19-16-27-01

角色

抽象处理者: 定义了处理请求的接口或者抽象类,提供了处理请求的的方法和设置下一个处理者的方法。

具体处理者: 实现或者继承抽象这角色,具体的实现处理逻辑.

实例与代码

责任链模式有纯的和不纯的.

纯的:当前接收者要么处理,要么传递给下一个处理者.

不纯的:当前接收者可以处理一部分之后交给下一个处理者.

明显不纯的更加受欢迎嘛,比如常见的企业OA审批流程,一般是请求->直系Leader同意->总监同意,而不是直系leader直接跳过.

下面将两种分别编码实现以下.

纯的

代码较多,想要更清晰可以移步github.传送门

这里我们用打印log来模拟一下场景: log有多种级别,debug,info,warn,error.对应着不同的处理方法 debug级别的不做处理. info打印即可. warn打印在标准输出之后写入文件保存. error错误发送邮件给项目owner.

首先实现一个抽象的LogHandler,里面定义了当前handler的级别,下一个处理者.

代码语言:javascript
复制
package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public abstract class AbstractLogHandler {

  public LevelEnum levelEnum;

  private AbstractLogHandler nextHandler;

  public void setNextHandler(
      AbstractLogHandler nextHandler) {
    this.nextHandler = nextHandler;
  }

  public void handlerRequest(LogInfo info) {
    if (null == info) {
      return;
    }
    if (this.levelEnum.equals(info.levelEnum)) {
      this.consumeLog(info.content);
    } else {
      if (this.nextHandler != null) {
        this.nextHandler.handlerRequest(info);
      } else {
        return;
      }
    }
  }

  abstract void consumeLog(String content);
}

接下来是四个具体的LogHandler实现.

DEBUG:

代码语言:javascript
复制
package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public class DebugLogHandler extends AbstractLogHandler {

  public DebugLogHandler() {
    this.levelEnum = LevelEnum.DEBUG;
  }

  @Override
  void consumeLog(String content) {
    //不做处理
    System.out.println("我是DEBUG,没有做处理");
  }
}

INFO:

代码语言:javascript
复制
package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public class InfoLogHandler extends AbstractLogHandler {

  public InfoLogHandler( ) {
    this.levelEnum = LevelEnum.INFO;
  }

  @Override
  void consumeLog(String content) {
    System.out.println(content);
    System.out.println("我是INFO,直接打印完事了");

  }
}

WARN:

代码语言:javascript
复制
package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public class WarnLogHandler extends AbstractLogHandler {

  public WarnLogHandler( ) {
    this.levelEnum = LevelEnum.WARN;
  }

  @Override
  void consumeLog(String content) {
    System.out.println(content);
    //写入文件
    System.out.println("我是WARN,不仅打印了还写入文件了.");
  }
}

ERROR:

代码语言:javascript
复制
package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public class ErrorLogHandler extends AbstractLogHandler{

  public ErrorLogHandler() {
    this.levelEnum = LevelEnum.ERROR;
  }

  @Override
  void consumeLog(String content) {
    System.out.println(content);
    System.out.println("我是ERROR,不仅打印了还写入文件了还发了个邮件.");

  }
}

下面是两个Model类:

代码语言:javascript
复制
package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public class LogInfo {

  public LevelEnum levelEnum;

  public String content;

}
代码语言:javascript
复制
package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public enum LevelEnum {
  DEBUG,
  INFO,
  WARN,
  ERROR;
}

测试代码:

代码语言:javascript
复制
package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public class Test {

  public static void main(String [] args ){
    //log信息
    LogInfo info = new LogInfo();
    info.content = "这是一条WARN测试log";
    info.levelEnum = LevelEnum.WARN;
    //第二条log信息
    LogInfo info1 = new LogInfo();
    info1.levelEnum = LevelEnum.ERROR;
    info1.content = "我是一条严重ERROR的LOG";


    //定义责任链
    AbstractLogHandler debugLog = new DebugLogHandler();
    AbstractLogHandler infoLog = new InfoLogHandler();
    AbstractLogHandler warnLog = new WarnLogHandler();
    AbstractLogHandler errorLog = new ErrorLogHandler();

    debugLog.setNextHandler(infoLog);
    infoLog.setNextHandler(warnLog);
    warnLog.setNextHandler(errorLog);

    debugLog.handlerRequest(info);
    debugLog.handlerRequest(info1);


  }

}

输出如下:

代码语言:javascript
复制
这是一条WARN测试log
我是WARN,不仅打印了还写入文件了.
我是一条严重ERROR的LOG
我是ERROR,不仅打印了还写入文件了还发了个邮件.

可以看出,在这样编码之后,在Test类中的代码清晰了许多.(去除掉了大量的if/else,同时,对责任链的初始化也可以移到别的类中,这里不做操作.)

同时,极大的提高了扩展性,假设现在出现了第五种log级别,我们只需要重新编写一个子类,然后再责任链中加入即可.

不纯的

代码较多,想要更清晰可以移步github.传送门

其实这个场景是我学习责任链的初衷,那就是在一个接口内部,我们需要对传入的多个参数(示例中防止代码过多,使用两个参数)进行校验,并返回不同的error_msg,如果在方法中实现,会有大量的if/else,而且你会发现对参数的校验代码比真正的业务逻辑都多.严重影响了代码的可读性.

下面用简单的代码来实现以下:

首先是三个model类:

代码语言:javascript
复制
package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public class BaseResponse {

  private int errCode;
  private String errMsg;

  private String content;

  public BaseResponse(BaseError error){
    this.errCode = error.errCode;
    this.errMsg = error.errMsg;
  }

  @Override
  public String toString() {
    return "errCode:" + errCode + ";" + "errMsg" + errMsg;
  }
}


package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public enum BaseError {
  SUCCESS(10000, "成功啦"),
  NAME_TOO_LONG(10001, "你的名字太长了8,不允许."),
  NAME_TOO_SHORT(10002, "你的名字不可以这么短"),
  AGE_TOO_BIG(10003, "千年老妖怪吗?"),
  AGE_TOO_SMALL(10004, "不支持-0.9以下的年龄哦"),
  HIGH_TOO_HIGH(10005, "我不管,姚明最高,再高不行"),
  HIGH_TOO_LOW(10006, "你个lowB,不准用.");

  int errCode;
  String errMsg;

  BaseError(int errCode, String errMsg) {
    this.errCode = errCode;
    this.errMsg = errMsg;
  }

}


package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public class Person {

  public String name;

  public int age;

  public int high;

  public Person(String name, int age, int high) {
    this.name = name;
    this.age = age;
    this.high = high;
  }
}

接下来是抽象的处理类:

代码语言:javascript
复制
package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public abstract class AbstractParamHandler {

  private AbstractParamHandler nextHandler;

  public void setNextHandler(
      AbstractParamHandler nextHandler) {
    this.nextHandler = nextHandler;
  }

  public BaseResponse handlerRequest(Person person) {
    BaseResponse response = doCheck(person);
    if (null == response) {
      if (this.nextHandler != null) {
        return this.nextHandler.handlerRequest(person);
      } else {
        return new BaseResponse(BaseError.SUCCESS);
      }
    }
    return response;
  }

  public abstract BaseResponse doCheck(Person person);
}

需要注意的是,这个代码和上面纯的责任链的区别,在这份代码中,没有进行是否是当前”级别”的判断,而是直接进行处理,如果当前参数校验不通过,直接返回,当前校验通过,继续进行下一次校验,如果全部通过,返回成功.

接下来是名字的处理类:

代码语言:javascript
复制
package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public class NameHandler extends AbstractParamHandler {

  @Override
  public BaseResponse doCheck(Person person) {
    if (person.name == null || person.name.length() < 1) {
      return new BaseResponse(BaseError.NAME_TOO_SHORT);
    } else if (person.name.length() > 10) {
      return new BaseResponse(BaseError.NAME_TOO_LONG);
    }
    return null;
  }
}

年龄的处理类:

代码语言:javascript
复制
package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public class AgeHandler extends AbstractParamHandler {

  @Override
  public BaseResponse doCheck(Person person) {
    if (person.age < -0.9) {
      return new BaseResponse(BaseError.AGE_TOO_SMALL);
    } else if (person.age > 1000) {
      return new BaseResponse(BaseError.AGE_TOO_BIG);
    }
    return null;
  }
}

身高的处理类:

代码语言:javascript
复制
package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public class HighHandler extends AbstractParamHandler {

  @Override
  public BaseResponse doCheck(Person person) {
    if (person.high < 40) {
      return new BaseResponse(BaseError.HIGH_TOO_LOW);
    } else if (person.high > 236) {
      return new BaseResponse(BaseError.HIGH_TOO_HIGH);
    }
    return null;
  }
}

测试代码:

代码语言:javascript
复制
package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public class Test {

  public static void main(String[] args) {

    AbstractParamHandler name = new NameHandler();
    AbstractParamHandler age = new AgeHandler();
    AbstractParamHandler high = new HighHandler();
    name.setNextHandler(age);
    age.setNextHandler(high);

    //成功案例
    Person person = new Person("huyanshi", 23, 172);
    System.out.println(name.handlerRequest(person));

    //名字太长案例
    Person person1 = new Person("huyanshihuyanshi",22 , 122);
    System.out.println(name.handlerRequest(person1));

    //年龄太小
    Person person2 = new Person("huyanshi",-10 , 122);
    System.out.println(name.handlerRequest(person2));
  }
}

对应的输出如下:

代码语言:javascript
复制
errCode:10000;errMsg成功啦
errCode:10001;errMsg你的名字太长了8,不允许.
errCode:10004;errMsg不支持-0.9以下的年龄哦

模式总结

优点:

  1. 请求的发起者不知道请求会被谁处理,解耦.
  2. 请求的处理者也不知道自己会处在链条的哪个位置,由客户端进行动态的装配.
  3. 系统的扩展性强,无论是新增处理者还是删除,甚至是打乱顺序重新组合,都十分方便.

缺点:

  1. 递归调用,可能带来性能问题.
  2. 递归调用,排查问题不方便.

思考

1. 和门面模式思想的结合

可以发现我们在Test类中的构造责任链的代码很麻烦,且重复的可能性较高,比如在每个项目中可能LOG的级别只有那么多,却需要每次装配一次.

我们可以再当前代码的基础上,提供一个四种级别LOG责任链的门面,当没有发生变化时,直接调用门面的构造方法,会自动装配这几种LOG级别并构造成链,这样可以复用代码,且没有破坏扩展性,当新增一个级别之后,我们可以选择建立新的门面,也可以选择修改旧的门面.都比较方便.

2. 和模板方法的区别与联系

如果看过模板方法模式,会发现责任链和模板方法有一点相似.

对应的关系为:

handlerRequest方法为父类具体方法. doCheck为父类抽象方法,每个子类必须自己去实现. setNextHandler为钩子方法,父类提供默认实现,子类可以实现可以不实现,当设置或者不设置,会产生控制流程的作用,即为钩子.

是不是很像呢?

这样结合模板方法模式的好处在哪?首先加了handlerRequest方法,把请求的传递判断从子类中剥离出来,让子类在doCheck方法中专心处理请求的业务逻辑,做到了单一职责原则。

果然是大道至简,万法归一啊…

完。

ChangeLog

2019-03-19 完成

以上皆为个人所思所得,如有错误欢迎评论区指正。

欢迎转载,烦请署名并保留原文链接。

联系邮箱:huyanshi2580@gmail.com

更多学习笔记见个人博客——>呼延十

var gitment = new Gitment({ id: '[设计模式] 责任链模式', // 可选。默认为 location.href owner: 'hublanker', repo: 'blog', oauth: { client_id: '2297651c181f632a31db', client_secret: 'a62f60d8da404586acc965a2ba6a6da9f053703b', }, }) gitment.render('container')



本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.介绍
  • 类图
  • 角色
  • 实例与代码
  • 纯的
  • 不纯的
  • 模式总结
  • 思考
    • 1. 和门面模式思想的结合
      • 2. 和模板方法的区别与联系
        • ChangeLog
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档