C#设计模式-责任链模式(Chain of Responsibility Pattern)

Tynam.Yang 2020-11-11 09:01:28
C# 设计 模式 责任


引子

一个事件需要经过多个对象处理是一个挺常见的场景,譬如采购审批流程,请假流程,软件开发中的异常处理流程,web请求处理流程等各种各样的流程,可以考虑使用责任链模式来实现。
现在以请假流程为例,一般公司普通员工的请假流程简化如下:
普通员工发起一个请假申请,当请假天数小于3天时只需要得到主管批准即可;当请假天数大于3天时,主管批准后还需要提交给经理审批,经理审批通过,若请假天数大于7天还需要进一步提交给总经理审批。

简单的流程可以通过 if-else 即可实现:

public class Leave
{
public void leaveApproval(int leaveDays)
{
if (leaveDays < 3)
{
Console.WriteLine("项目经理审批");
}
else if (leaveDays < 7)
{
Console.WriteLine("部门经理审批");
}
else if (leaveDays < 30)
{
Console.WriteLine("总经理审批");
}
else
{
Console.WriteLine("审批困难");
}
}
}

但是这样的写法看起来简单,后续维护难度却是不少。可以看出代码臃肿且耦合度高。

  • 代码臃肿:实际应用中的判定条件通常不是这么简单地判断,也许需要复杂的计算,也许需要查询数据库等等,这就会有很多额外的代码,如果判断条件再比较多,那么这个if…else…语句基本上就没法看了。
  • 耦合度高:如果我们想继续添加处理请求的类,那么就要继续添加else if判定条件;另外,这个条件判定的顺序也是写死的,如果想改变顺序,那么也只能修改这个条件语句。

在设计模式中提倡单一职责原则,如果项目组内再加一个组长,审批请假小于一天的呢?此时就会感觉 if-else 灵活性太差,修改代码后测试需要重新测试全部流程才能保证质量。
既然已经清楚他的不足,则针对此业务逻辑可以稍作转换:如果满足条件1,则由 Handler1 来处理,不满足则向下传递;如果满足条件2,则由 Handler2 来处理,不满足则继续向下传递,以此类推,直到条件结束。其实改进的方法也很简单,就是把判定条件的部分放到处理类中,这就是责任连模式的原理。

定义

责任链模式属于行为类模式。使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
责任链模式把多个处理器串成链,然后让请求在链上传递:

类图

从的定义可以看出涉及的对象只有处理者角色,但可以有多个处理者,这些处理者做的事情都是一样的,处理请求的方法,所以可以抽象出一个处理者角色进行代码复用。如下类图。

角色

  • Handler(抽象处理类):抽象处理类中主要包含一个指向下一处理类的成员变量nextHandler和一个处理请求的方法handRequest,handRequest方法的主要主要思想是,如果满足处理的条件,则有本处理类来进行处理,否则由nextHandler来处理。
  • ConcreteHandler(具体处理类):具体处理类主要是对具体的处理逻辑和处理的适用条件进行实现。

实现

将上面的请假流程重新梳理,使用责任链模式进行实现:

using System;
namespace 责任链模式
{
class Program
{
static void Main(string[] args)
{
LeaveRequest leaveTwoDays = new LeaveRequest(2, "grey1");
LeaveRequest leaveSixDays = new LeaveRequest(6, "grey2");
LeaveRequest leaveEightDays = new LeaveRequest(8, "grey3");
Approver PM = new Manager("jon1");
Approver DM = new DepartmentManager("jon2");
Approver GM = new GeneralManager("jon3");
// 设置责任链
PM.NextApprover = DM;
DM.NextApprover = GM;
// 处理请求
 PM.LeaveRequest(leaveTwoDays);
PM.LeaveRequest(leaveSixDays);
PM.LeaveRequest(leaveEightDays);
Console.ReadLine();
}
}
// 请假需求
public class LeaveRequest
{
public int Day { get; set; }
public string Name { get; set; }
public LeaveRequest(int day, string name)
{
this.Day = day;
this.Name = name;
}
}
// 审批人
public abstract class Approver {
public Approver NextApprover { get; set; }
public string Name { get; set; }
public Approver(string name)
{
this.Name = name;
}
public abstract void LeaveRequest(LeaveRequest requeset);
}
// 项目经理
public class Manager : Approver
{
public Manager(string name)
: base(name) { }
public override void LeaveRequest(LeaveRequest requeset)
{
if (requeset.Day < 3)
{
Console.WriteLine("项目经理 {0} 审批 {1} 请假", this.Name, requeset.Name);
}
else
{
NextApprover.LeaveRequest(requeset);
}
}
}
// 部门经理
public class DepartmentManager : Approver
{
public DepartmentManager(string name)
: base(name) { }
public override void LeaveRequest(LeaveRequest requeset)
{
if (requeset.Day < 7)
{
Console.WriteLine("部门经理 {0} 审批 {1} 请假", this.Name, requeset.Name);
}
else
{
NextApprover.LeaveRequest(requeset);
}
}
}
// 总经理
public class GeneralManager : Approver
{
public GeneralManager(string name)
: base(name) { }
public override void LeaveRequest(LeaveRequest requeset)
{
if (requeset.Day < 30)
{
Console.WriteLine("总经理 {0} 审批 {1} 请假", this.Name, requeset.Name);
}
else
{
Console.WriteLine("审批困难"); ;
}
}
}
}

运行一下:

项目经理 jon1 审批 grey1 请假
部门经理 jon2 审批 grey2 请假
总经理 jon3 审批 grey3 请假

LeaveRequest 类为请求请假。
Approver 为处理人员。并且设置了三个处理人员,Manager、DepartmentManager、GeneralManager。
设置的责任链为 Manager-->DepartmentManager-->GeneralManager。当发生请假请求时首先由Manager进行处理,处理不了转由DepartmentManager,如果DepartmentManager还是处理不了则继续向更好职位的人员GeneralManager进行提交,由更大权限的人进行处理。
实现的功能和文章最初的 if...else 一样。但时可以看到使用责任链模式代码更清楚,请求发送者是发送者,接收者是接收者。

适用场景 

通过上面的定义、类图及示例可以考虑责任链模式适用的场景:

  • 在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。
  • 代码中存在多个if-else语句的情况下,此时可以考虑使用责任链模式来对代码进行重构。
  • 可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序。

优缺点

通过上面的介绍很容易发现,责任链模式的优点:

  • 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
  • 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
  • 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
  • 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
  • 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

但也有缺点:

  • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

扩展

纯的责任链模式:

  • 一个具体处理者对象只能在两个行为中选择一个:要么承担全部责任,要么将责任推给下家,不允许出现某一个具体处理者对象在承担了一部分或全部责任后 又将责任向下传递的情况。
  • 一个请求必须被某一个处理者对象所接收,不能出现某个请求未被任何一个处理者对象处理的情况。

不纯的责任链模式:

  • 允许某个请求被一个具体处理者部分处理后再向下传递。
  • 或者一个具体处理者处理完某请求后其后继处理者可以继续处理该请求。
  • 而且一个请求可以最终不被任何处理者对象所接收。

总结

责任链模式其实就是一个灵活版的if…else…语句,将这些判定条件的语句放到了各个处理类中,非常灵活。
责任链模式是一种把多个处理器组合在一起,依次处理请求的模式。
责任链降低了请求端和接收端之间的耦合,使多个对象都有机会处理某个请求。
责任链模式经常用在拦截、预处理请求等。
与此同样也带来了风险,比如设置处理类前后关系时,一定要特别仔细,搞对处理类前后逻辑的条件判断关系,并且注意不要在链中出现循环引用的问题。

参考

https://juejin.im/post/6844903702260629512
https://www.w3cschool.cn/javadesignpattern/omas1ii2.html
https://www.cnblogs.com/zhili/p/ChainOfResponsibity.html
http://c.biancheng.net/view/1383.html
https://www.liaoxuefeng.com/wiki/1252599548343744/1281319474561057

 

版权声明
本文为[Tynam.Yang]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/tynam/p/13953789.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云