• 责任链模式


    老板小张,这里有个需求需要你来完成。

    张工张工已就位,什么需求我康康

    老板

    我们的客户信息都是先统计在Excel表格中,但是客服小姐姐有时候会不小心把客户信息填错,这些数据在经过我们的系统我不希望数据都成功录入,并且那些错误的信息要能够提示出来好进行修改!这才银杏!!

    客服老板~ 那么多数据我怎么能做到百分百正确率啊,眼睛都要看花呐~

    15分钟后

    张工默默的忍受这一切,抱怨客服工作不认真,抱怨老板........终于张工在沉默中爆发了!

    张工保证完成任务!

    张工心里想着不对劲啊,为什么会有这么简单的功能,连客户信息都不做校验!难道出BUG了!!?张工将代码从仓库拉取到本地...看着前辈的代码,挺简洁的呀!就是看不到做信息校验逻辑代码的影子...于是直接在导入数据的Service中写上了客户各种信息校验的代码,完成!

    代码如下:(示例代码)

    • User
    1. package com.zwf.responsibilitychain.po;
    2. /**
    3. * 客户类
    4. * @author zwf_xiaozhang
    5. */
    6. public class User {
    7. /**
    8. * 电话号码
    9. */
    10. private String tel;
    11. /**
    12. * 姓名
    13. */
    14. private int age;
    15. /**
    16. * 错误信息
    17. */
    18. private String errorInfo;
    19. /**
    20. * 状态:1 0
    21. * 1:信息全部校验通过 0:有一个信息校验未通过
    22. */
    23. private int state;
    24. public User(String tel, int age) {
    25. this.tel = tel;
    26. this.age = age;
    27. }
    28. public String getTel() {
    29. return tel;
    30. }
    31. public void setTel(String tel) {
    32. this.tel = tel;
    33. }
    34. public int getAge() {
    35. return age;
    36. }
    37. public void setAge(int age) {
    38. this.age = age;
    39. }
    40. public String getErrorInfo() {
    41. return errorInfo;
    42. }
    43. public void setErrorInfo(String errorInfo) {
    44. this.errorInfo = errorInfo;
    45. }
    46. public int getState() {
    47. return state;
    48. }
    49. public void setState(int state) {
    50. this.state = state;
    51. }
    52. @Override
    53. public String toString() {
    54. return "User{" +
    55. "tel='" + tel + '\'' +
    56. ", age=" + age +
    57. ", errorInfo='" + errorInfo + '\'' +
    58. ", state=" + state +
    59. '}';
    60. }
    61. }
    • Service
    1. package com.zwf.responsibilitychain;
    2. import com.zwf.responsibilitychain.po.User;
    3. import java.util.ArrayList;
    4. import java.util.List;
    5. import java.util.stream.Collectors;
    6. public class Test1 {
    7. public static void main(String[] args) {
    8. //模拟数据(前端获取)
    9. User u = new User("123451673674",112);
    10. String tel = u.getTel();
    11. int age = u.getAge();
    12. //报错错误信息集合
    13. List errorInfo = new ArrayList<>();
    14. //校验年龄
    15. if(age<0 || age > 100){
    16. u.setState(1);
    17. errorInfo.add("年龄有问题!");
    18. }
    19. //校验电话号码
    20. if(tel.length() != 11){
    21. u.setState(1);
    22. errorInfo.add("电话格式有问题");
    23. }
    24. //拼接错误信息并赋值
    25. u.setErrorInfo(errorInfo.stream().collect(Collectors.joining(",")));
    26. System.out.println(u);
    27. }
    28. }

    随着业务的复杂度递增,张工继续写着相似的许多代码,虽然只是copy。然后张工逐渐发现了代码的问题!耦合度太高,代码复用率太低...在其他模块都有着相似的校验,有一些校验逻辑非常复杂,甚至出现了if else嵌套有三层,过几天就忘了,要拓展逻辑业务还得重新理解这些嵌套的if else,然后再将新的逻辑添加进去,修改起来及其复杂!!!张工留下了后悔的眼泪...

    设计模式要你康康我你不看,活该!

    学习设计模式中ing...

    于是张工重构了代码,如下:

    UML图

    将校验这个功能抽象出来成一个抽象类,子类继承然后实现具体的校验逻辑,比如TelHandler就做电话的校验处理。nextHandler作为一个连接点连接到下一个校验子类,就这样可以形成一条责任链。我们可以根据自己的业务需求来组合我们的责任链,这里就可以配合简单工厂模式,我们的子类其实和策略模式的策略大同小异,也可以写成单例的。为什么写成单例?看以下分析:

    调用流程图:

    如果在多线程情况下,每个线程来访问service,我们需要不断的new具体的处理器,然后进行组合,其实new出了许多重复的子类然后组合,那为什么不优化成单例的呢?减少程序运行中的内存开销。

    最终的UML图如下:

    代码如下:

    • User
    1. package com.zwf.responsibilitychain.po;
    2. /**
    3. * 客户类
    4. * @author zwf_xiaozhang
    5. */
    6. public class User {
    7. /**
    8. * 电话号码
    9. */
    10. private String tel;
    11. /**
    12. * 姓名
    13. */
    14. private int age;
    15. /**
    16. * 错误信息
    17. */
    18. private String errorInfo;
    19. /**
    20. * 状态:1 0
    21. * 1:信息全部校验通过 0:有一个信息校验未通过
    22. */
    23. private int state;
    24. public User(String tel, int age) {
    25. this.tel = tel;
    26. this.age = age;
    27. }
    28. public String getTel() {
    29. return tel;
    30. }
    31. public void setTel(String tel) {
    32. this.tel = tel;
    33. }
    34. public int getAge() {
    35. return age;
    36. }
    37. public void setAge(int age) {
    38. this.age = age;
    39. }
    40. public String getErrorInfo() {
    41. return errorInfo;
    42. }
    43. public void setErrorInfo(String errorInfo) {
    44. this.errorInfo = errorInfo;
    45. }
    46. public int getState() {
    47. return state;
    48. }
    49. public void setState(int state) {
    50. this.state = state;
    51. }
    52. @Override
    53. public String toString() {
    54. return "User{" +
    55. "tel='" + tel + '\'' +
    56. ", age=" + age +
    57. ", errorInfo='" + errorInfo + '\'' +
    58. ", state=" + state +
    59. '}';
    60. }
    61. }
    • AgeHandler extends UserInfoHandler
    1. package com.zwf.responsibilitychain.handler;
    2. import com.zwf.responsibilitychain.po.User;
    3. import com.zwf.responsibilitychain.UserInfoHandler;
    4. import org.springframework.stereotype.Component;
    5. /**
    6. * 年龄校验处理器
    7. * @author zwf_xiaozhang
    8. * 说明:单例对象,饿汉模式加载,单列对象注册到工厂的容器当中
    9. */
    10. public class AgeHandler extends UserInfoHandler {
    11. /**
    12. * 单例对象
    13. */
    14. private static final AgeHandler ageHandler = new AgeHandler();
    15. /**
    16. * 校验用户年龄
    17. * 这里仅做案例,简单实现
    18. * @param user
    19. */
    20. @Override
    21. public void checkInfo(User user) {
    22. int age = user.getAge();
    23. if(age<0 || age > 100){
    24. errorInfos.add("年龄有问题");
    25. user.setState(1);
    26. }else if(nextHandler != null){
    27. nextHandler.checkInfo(user);
    28. }
    29. }
    30. /**
    31. * 构造私有化 调用构造方法的时候注册
    32. */
    33. private AgeHandler() {
    34. register();
    35. }
    36. /**
    37. * 对外开发的获取单例对象的方法
    38. * @return
    39. */
    40. public static AgeHandler getInstance(){
    41. return ageHandler;
    42. }
    43. }
    • TelHandler extends UserInfoHandler
    1. package com.zwf.responsibilitychain.handler;
    2. import com.zwf.responsibilitychain.po.User;
    3. import com.zwf.responsibilitychain.UserInfoHandler;
    4. import org.springframework.stereotype.Component;
    5. /**
    6. * 电话校验处理器
    7. * @author zwf_xiaozhang
    8. * 说明:单例对象,饿汉模式加载,单列对象注册到工厂的容器当中
    9. */
    10. public class TelHandler extends UserInfoHandler {
    11. /**
    12. * 单例对象
    13. */
    14. private static final TelHandler telHandler = new TelHandler();
    15. /**
    16. * 校验用户电话号码
    17. * 这里仅做案例,简单实现
    18. * @param user
    19. */
    20. @Override
    21. public void checkInfo(User user) {
    22. String tel = user.getTel();
    23. if(tel.length() != 11){
    24. errorInfos.add("电话号码格式错误");
    25. user.setState(1);
    26. }else if(nextHandler != null){
    27. nextHandler.checkInfo(user);
    28. }
    29. }
    30. /**
    31. * 构造私有化 调用构造方法的时候注册
    32. */
    33. private TelHandler() {
    34. register();
    35. }
    36. /**
    37. * 对外开发的获取单例对象的方法
    38. * @return
    39. */
    40. public static TelHandler getInstance(){
    41. return telHandler;
    42. }
    43. }
    • UserInfoHandler
    1. package com.zwf.responsibilitychain;
    2. import com.zwf.responsibilitychain.po.User;
    3. import java.util.ArrayList;
    4. import java.util.List;
    5. /**
    6. * 用户信息处理接口
    7. * @author zwf_xiaozhang
    8. */
    9. public abstract class UserInfoHandler {
    10. /**
    11. * 错误信息容器,值初始化一次。
    12. */
    13. protected static final List errorInfos = new ArrayList<>();
    14. /**
    15. * 指向下一个处理器
    16. */
    17. protected UserInfoHandler nextHandler;
    18. public void setNextHandler(UserInfoHandler nextHandler){
    19. this.nextHandler = nextHandler;
    20. }
    21. /**
    22. * 校验用户信息
    23. * @param user
    24. */
    25. public abstract void checkInfo(User user);
    26. /**
    27. * 注册方法
    28. */
    29. public void register(){
    30. UserInfoChainFactory.registerHandler(getClass().getSimpleName(),this);
    31. }
    32. }
    • UserInfoChainFactory
    1. package com.zwf.responsibilitychain;
    2. import java.util.*;
    3. /**
    4. * 责任链工厂
    5. * @author zwf_xiaozhang
    6. */
    7. public class UserInfoChainFactory {
    8. /**
    9. * 处理器容器
    10. */
    11. private static final Map handlerMap = new HashMap<>();
    12. /**
    13. * 根据名称获取单个处理器
    14. * @param name
    15. * @return
    16. */
    17. public static UserInfoHandler getHandler(String name){
    18. return handlerMap.get(name);
    19. }
    20. /**
    21. * 类注册方法
    22. * @param handlerName
    23. * @param handler
    24. */
    25. public static void registerHandler(String handlerName, UserInfoHandler handler){
    26. handlerMap.put(handlerName.toUpperCase(Locale.ROOT),handler);
    27. }
    28. /**
    29. * 根据前端给的参数进行责任链的拼接
    30. * @param names 处理器的名称
    31. * @return
    32. */
    33. public static UserInfoHandler getUserInfoChain(List names){
    34. //获取到第一个处理器
    35. UserInfoHandler handler = handlerMap.get(names.remove(0));
    36. //设置零时处理器引用
    37. UserInfoHandler tempHandler = null;
    38. //拼接处理器形成责任链
    39. for(int i = 0; i < names.size(); i++){
    40. if(i == 0){
    41. handler.nextHandler = handlerMap.get(names.get(i));
    42. }else{
    43. tempHandler.nextHandler = handlerMap.get(names.get(i));
    44. }
    45. tempHandler = handlerMap.get(names.get(i));
    46. }
    47. return handler;
    48. }
    49. }
    • Test -- 单线程情景 
    1. package com.zwf.responsibilitychain;
    2. import com.zwf.responsibilitychain.handler.AgeHandler;
    3. import com.zwf.responsibilitychain.handler.TelHandler;
    4. import com.zwf.responsibilitychain.po.User;
    5. import java.util.ArrayList;
    6. import java.util.List;
    7. import java.util.stream.Collectors;
    8. public class Test {
    9. public static void main(String[] args) {
    10. //初始化处理器
    11. AgeHandler.getInstance();
    12. TelHandler.getInstance();
    13. //模拟数据,一般从前端直接获取到的需要校验的用户
    14. User u =new User("1245678901",12);
    15. List names = new ArrayList<>();
    16. names.add("ageHandler");
    17. names.add("telHandler");
    18. //将处理器名称全部转大写,在注册的时候其实也都转了大写,防止单词大小写写错导致获取不到处理器
    19. names = names.stream().map(String::toUpperCase).collect(Collectors.toList());
    20. //获取责任链
    21. UserInfoHandler handler = UserInfoChainFactory.getUserInfoChain(names);
    22. //开始检验
    23. handler.checkInfo(u);
    24. //设置错误信息
    25. u.setErrorInfo(UserInfoHandler.errorInfos.stream().collect(Collectors.joining(",")));
    26. //清除集合
    27. UserInfoHandler.errorInfos.clear();
    28. System.out.println(u);
    29. }
    30. }
    31. }

    补充:

    当AgeHandler、TelHandler两个类被加载时就会初始化然后注册到工厂的容器中。为了方便测试我直接写在main方法中运行,不是启动这个工程,所以这些类不会加载,需要我进行初始化。如果时Spring工程,配合@Compent注解将处理器进行注入就行。

    工厂接收字符串集合的逻辑是:

    由前端传过来的字符串集合定义需要进行哪些校验然后由工厂生产对应的责任链。这样设计的目的,实现该方法在后台的高复用率,因为业务场景中会出现不同类型的客户,就需要不同的责任链,如果每个责任链写一个接口或者Service,没必要了,直接由前端定义好需要校验的信息,然后工厂生产责任链即可。

    公共集合errorInfos的作用:

    将收集到的错误信息用","拼接设置到该对象的errorInfo属性,返回给前端作为提示!

    为什么要clear()集合?

    因为这个集合是被 static final修饰的,在程序运行中只有一个集合!是所有责任链节点共有的!每一个责任链处理完要将其clear()还原,不然下一个责任链使用时会出现bug。

    为什么写成共有的集合 static final ?

    防止高并发场景造成内存溢出!如果集合不是共有的,每次调用都需要创建一个集合,需要很大的内存开销。

    • Test--多线程场景
    1. package com.zwf.responsibilitychain;
    2. import com.zwf.responsibilitychain.handler.AgeHandler;
    3. import com.zwf.responsibilitychain.handler.TelHandler;
    4. import com.zwf.responsibilitychain.po.User;
    5. import java.util.ArrayList;
    6. import java.util.List;
    7. import java.util.stream.Collectors;
    8. public class Test {
    9. public static void main(String[] args) {
    10. //初始化
    11. AgeHandler.getInstance();
    12. TelHandler.getInstance();
    13. //模拟多线程情景
    14. for (int i = 0; i < 1000; i++){
    15. new Thread(()->{
    16. //模拟数据,一般从前端直接获取到的需要校验的用户
    17. User u =new User("1245678901",12);
    18. List names = new ArrayList<>();
    19. names.add("ageHandler");
    20. names.add("telHandler");
    21. names = names.stream().map(String::toUpperCase).collect(Collectors.toList());
    22. //获取责任链
    23. UserInfoHandler handler = UserInfoChainFactory.getUserInfoChain(names);
    24. synchronized (Test.class){
    25. //开始检验
    26. handler.checkInfo(u);
    27. //设置错误信息
    28. u.setErrorInfo(UserInfoHandler.errorInfos.stream().collect(Collectors.joining(",")));
    29. //清除集合
    30. UserInfoHandler.errorInfos.clear();
    31. System.out.println(u);
    32. }
    33. }).start();
    34. }
    35. }
    36. }

    大家也知道,上锁会导致效率低的问题,如果一个线程就要处理大量用户数据,那么效率更低!

    浅分析一下:

    10个线程,每个线程要校验3w用户数据,这3w数据的每一条数据循环使用这个集合。其他线程要等前面线程校验完了才能使用到这个集合进行校验处理,如果3w要校验6s,10个线程就说一分钟!!!

    我有想到一个优化方案:ThreadLocal,将这个集合绑定到我们的线程上!下期更新!

    张工呼~ 只能优化到这了,我要先去跟线程池大哥混,然后找他帮忙做这个优化

    设计模式现在知道我有多香了吧,我一共有23款!你自己慢慢品吧...

    上线后...

    客服比之前好用了,没用脏数据了,但是处理起来比以前慢了~

    老板小张,抽空去优化一下!!!生产队的驴都不敢这么歇!

    张工没问题...

    张工看到了成堆的需求,看到了没有注释的代码,看到了摸鱼的产品,看到了...

    冷静的泡了一杯咖啡,坐在角落里的工位,然后默默的打开了招聘网...

    故事纯属虚构请勿当真...

    重要的是设计模式的学习...

  • 相关阅读:
    第十章《日期与时间》第1节:Date类的使用
    STM32F407单片机HAL库CAN2不能接收数据解决方法
    vue中组件模板为什么只有一个根元素?
    【云原生之k8s】KubeSphere介绍及安装
    产品经理-研发流程-敏捷开发-迭代-需求评审及产品规划(15)
    Node.js 入门教程 16 npm 将软件包安装到哪里
    手机便签功能在哪里?如何在便签里添加文字图片视频?
    从一道题到贪心入门
    CSS 多行文本超链接下划线动效
    face_recognition库实现人脸识别demo
  • 原文地址:https://blog.csdn.net/weixin_49789131/article/details/126838907