设计模式的征途—14.职责链(Chain of Responsibility)模式
相信大家都玩过类似于“斗地主”的纸牌游戏,某人出牌给他的下家,下家看看手中的牌,如果要不起,则将出牌请求转发给他的下家,其下家再进行判断。一个循环下来,如果其他人都要不起该牌,则最初的出牌者可以打出新牌。在这个过程中,纸牌作为一个请求沿着一条链在传递,每一位纸牌的玩家都可以处理该请求。在设计模式中,也有一种专门用于处理这种请求链式的模式,它就是职责链模式。
职责链模式(Chain of Responsibility) | 学习难度:★★★☆☆ | 使用频率:★★☆☆☆ |
一、采购单的分级审批模块设计
需求背景:M公司承接了某企业SCM(Supply Chain Management,供应链管理)系统的开发任务,其中包含一个采购审批子系统。该企业的采购审批是分级进行的,即根据采购金额的不同由不同层次的主管人员来审批:主任可以审批5万元以下(不包括5万)的采购单,副董事长可以审批5万~10万(不包括10万)的采购单,50万元以及以上的采购单就需要开董事会讨论决定,如下图所示:
M公司开发人员提出了一个初始解决方案,提供了一个采购单处理类PurchaseRequestHandler用于统一处理采购单,其框架代码如下:
/// <summary> /// 采购单处理类 /// </summary> public class PurchaseRequestHandler { // 递交采购单给审批者 public void SendRequestToApprover(PurchaseRequest request) { if (request.Amount < 5000) // 主任可审批该采购单 { HandleByDirector(request); } else if(request.Amount < 100000) // 副董事长可审批该采购单 { HandleByVicePresident(request); } else if (request.Amount < 500000) // 董事长可审批该采购单 { HandleByPresident(request); } else { HandleByCongress(request); // 董事会可审批该采购单 } } // 主管审批采购单 private void HandleByDirector(PurchaseRequest request) { // 代码省略 } // 副董事长审批采购单 private void HandleByVicePresident(PurchaseRequest request) { // 代码省略 } // 董事长审批采购单 private void HandleByPresident(PurchaseRequest request) { // 代码省略 } // 董事会审批采购单 private void HandleByCongress(PurchaseRequest request) { // 代码省略 } }
不过仔细分析后发现,上述方案存在以下3个问题:
(1)PurchaseRequestHandler类较为庞大,各个级别的审批方法都集中在一个类中,违反了单一职责原则,测试和维护难度较大。
(2)如果需要新增一个新的审批级别或调整任何一级的审批金额和审批细节时都必须修改源代码并进行严格测试。此外,如果需要移除某一级别时也需要对源代码进行修改,违反了开闭原则。
(3)审批流程的设置缺乏灵活性,现在的审批流程是“主任->副董事长->董事长->董事会”,如果需要改为“主任->董事长->董事会”,在此方案中只能通过修改源代码来实现,客户端无法定制审批流程。
那么如何破呢?别急,来看看职责链模式。
二、职责链模式概述
2.1 职责链模式简介
职责链(Chain of Responsibility)模式:避免将请求发送者与接受者耦合在一起,让多个对象都有机会接受请求,将这些对象连成一条链,并且沿着这条链传递请求,直到有对象处理它为止。职责链模式是一种对象行为型模式。
2.2 职责链模式结构
职责链模式结构的核心就在于引入了一个抽象处理者,其结构如下图所示:
在职责链模式结构图中包含以下两个角色:
(1)Handler(抽象处理者):定义了一个处理请求的接口,一般设计为抽象类,由于不同的具体处理者处理请求的方式不同,因此在其中定义了抽象请求处理方法。
(2)ConcreteHandler(具体处理者):它是抽象处理者的子类,可以处理用户请求,它实现了在抽象处理者中定义的抽象请求处理方法。在处理请求之前需要判断是否有相应的处理权限,如果可以则处理,否则则将请求转发给后继者。
三、重构采购单分级审批模块
3.1 重构后的设计
其中,抽象类Approver充当抽象处理类,Director, VicePresident, President以及Congress 充当具体处理者,PurchaseRequest充当请求类。
3.2 具体代码实现
(1)请求类:PurchaseRequest
/// <summary> /// 采购单:请求类 /// </summary> public class PurchaseRequest { // 采购金额 public double Amount { get; set; } // 采购单编号 public string Number { get; set; } // 采购目的 public string Purpose { get; set; } public PurchaseRequest(double amount, string number, string purpose) { Amount = amount; Number = number; Purpose = purpose; } }
(2)抽象处理者:Approver
/// <summary> /// 审批者类:抽象处理者 /// </summary> public abstract class Approver { protected Approver successor; // 定义后继对象 protected string name; // 审批者姓名 public Approver(string name) { this.name = name; } // 设置后继者 public void SetSuccessor(Approver successor) { this.successor = successor; } // 抽象请求处理方法 public abstract void ProcessRequest(PurchaseRequest request); }
(3)具体处理者:Director, VicePresident, President以及Congress
/// <summary> /// 总监:具体处理类 /// </summary> public class Director : Approver { public Director(string name) : base(name) { } // 具体请求处理方法 public override void ProcessRequest(PurchaseRequest request) { if (request.Amount < 50000) { // 处理请求 Console.WriteLine("主管 {0} 审批采购单:{1},金额:{2} 元,采购目的:{3}。", this.name, request.Number, request.Amount, request.Purpose); } else { // 如果处理不了,转发请求给更高层领导 this.successor.ProcessRequest(request); } } } /// <summary> /// 副总裁:具体处理类 /// </summary> public class VicePresident : Approver { public VicePresident(string name) : base(name) { } // 具体请求处理方法 public override void ProcessRequest(PurchaseRequest request) { if (request.Amount < 100000) { // 处理请求 Console.WriteLine("副总裁 {0} 审批采购单:{1},金额:{2} 元,采购目的:{3}。", this.name, request.Number, request.Amount, request.Purpose); } else { // 如果处理不了,转发请求给更高层领导 this.successor.ProcessRequest(request); } } } /// <summary> /// 总裁:具体处理者 /// </summary> public class President : Approver { public President(string name) : base(name) { } // 具体请求处理方法 public override void ProcessRequest(PurchaseRequest request) { if (request.Amount < 500000) { // 处理请求 Console.WriteLine("总裁 {0} 审批采购单:{1},金额:{2} 元,采购目的:{3}。", this.name, request.Number, request.Amount, request.Purpose); } else { // 如果处理不了,转发请求给更高层领导 this.successor.ProcessRequest(request); } } } /// <summary> /// 董事会:具体处理者 /// </summary> public class Congress : Approver { public Congress(string name) : base(name) { } // 具体请求处理方法 public override void ProcessRequest(PurchaseRequest request) { // 处理请求 Console.WriteLine("董事会 {0} 审批采购单:{1},金额:{2} 元,采购目的:{3}。", this.name, request.Number, request.Amount, request.Purpose); } }
(4)客户端测试:
public class Program { public static void Main(string[] args) { // 创建职责链 Approver andy = new Director("Andy"); Approver jacky = new VicePresident("Jacky"); Approver ashin = new President("Ashin"); Approver meeting = new Congress("Congress"); andy.SetSuccessor(jacky); jacky.SetSuccessor(ashin); ashin.SetSuccessor(meeting); // 构造采购请求单并发送审批请求 PurchaseRequest request1 = new PurchaseRequest(45000.00, "MANULIFE201706001", "购买PC和显示器"); andy.ProcessRequest(request1); PurchaseRequest request2 = new PurchaseRequest(60000.00, "MANULIFE201706002", "2017开发团队活动"); andy.ProcessRequest(request2); PurchaseRequest request3 = new PurchaseRequest(160000.00, "MANULIFE201706003", "2017公司年度旅游"); andy.ProcessRequest(request3); PurchaseRequest request4 = new PurchaseRequest(800000.00, "MANULIFE201706004", "租用新临时办公楼"); andy.ProcessRequest(request4); Console.ReadKey(); } }
编译运行后的结果如下图所示:
3.3 需求扩展实现
这时,假设需要在系统中新增一个新的具体处理者,例如增加一个经理(Manager)角色可以审批5万~8万(不包括8万)的采购单。因此,我们可以新增一个具体处理者:Manager
/// <summary> /// 经理:具体处理者 /// </summary> public class Manager : Approver { public Manager(string name) : base(name) { } // 具体请求处理方法 public override void ProcessRequest(PurchaseRequest request) { if (request.Amount < 80000) { // 处理请求 Console.WriteLine("经理 {0} 审批采购单:{1},金额:{2} 元,采购目的:{3}。", this.name, request.Number, request.Amount, request.Purpose); } else { this.successor.ProcessRequest(request); } } }
由于链的创建过程由客户端负责,因此此扩展对原有类库无任何影响,符合开闭原则。而我们需要做的,仅仅是在客户端代码中新增职责链关系的创建即可。
public class Program { public static void Main(string[] args) { // 创建职责链 Approver andy = new Director("Andy"); Approver jacky = new Manager("Jacky"); Approver ashin = new VicePresident("Ashin"); Approver anya = new President("Anya"); Approver meeting = new Congress("Congress"); andy.SetSuccessor(jacky); jacky.SetSuccessor(ashin); ashin.SetSuccessor(anya); anya.SetSuccessor(meeting); // 构造采购请求单并发送审批请求 PurchaseRequest request1 = new PurchaseRequest(45000.00, "MANULIFE201706001", "购买PC和显示器"); andy.ProcessRequest(request1); PurchaseRequest request2 = new PurchaseRequest(60000.00, "MANULIFE201706002", "2017开发团队活动"); andy.ProcessRequest(request2); PurchaseRequest request3 = new PurchaseRequest(160000.00, "MANULIFE201706003", "2017公司年度旅游"); andy.ProcessRequest(request3); PurchaseRequest request4 = new PurchaseRequest(800000.00, "MANULIFE201706004", "租用新临时办公楼"); andy.ProcessRequest(request4); Console.ReadKey(); } }
重新编译运行后的结果如下图所示:
四、职责链模式总结
4.1 主要优点
(1)使得一个对象无需知道是其他哪一个对象处理其请求,对象仅需知道该请求会被处理即可,且链式结构由客户端创建 => 降低了系统的耦合度
(2)在系统中增加一个新的具体处理者无须修改原有系统源代码,只需要在客户端重新建立链式结构即可 => 符合开闭原则
4.2 主要缺点
(1)由于一个请求没有一个明确地接受者 => 无法保证它一定会被处理
(2)对于较长的职责链 => 系统性能有一定影响且不利于调试
(3)如果建立链不当,可能会造成循环调用 => 导致系统进入死循环
4.3 应用场景
(1)有多个对象处理同一个请求且无需关心请求的处理对象时谁以及它是如何处理的 => 比如各种审批流程
(2)可以动态地指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序 => 比如各种流程定制
参考资料
刘伟,《设计模式的艺术—软件开发人员内功修炼之道》