C#设计模式-责任链模式(Chain of Responsibility Pattern)
引子
一个事件需要经过多个对象处理是一个挺常见的场景,譬如采购审批流程,请假流程,软件开发中的异常处理流程,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…语句,将这些判定条件的语句放到了各个处理类中,非常灵活。
责任链模式是一种把多个处理器组合在一起,依次处理请求的模式。
责任链降低了请求端和接收端之间的耦合,使多个对象都有机会处理某个请求。
责任链模式经常用在拦截、预处理请求等。
与此同样也带来了风险,比如设置处理类前后关系时,一定要特别仔细,搞对处理类前后逻辑的条件判断关系,并且注意不要在链中出现循环引用的问题。
参考
//juejin.im/post/6844903702260629512
//www.w3cschool.cn/javadesignpattern/omas1ii2.html
//www.cnblogs.com/zhili/p/ChainOfResponsibity.html
//c.biancheng.net/view/1383.html
//www.liaoxuefeng.com/wiki/1252599548343744/1281319474561057