1、概述
在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假或者加薪,可处理的领导有HR、部门负责人、副总经理、总经理等,但每个领导能批准的天数和加薪额度不同,员工必须根据自己的情况去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花”游戏、发薪资等。
定义
又名责任链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
职责链模式主要包含以下角色:
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
2、实现思路
场景
现需要开发一个请假流程控制系统。请假一天以下的假只需要主管同意即可;请假2天需要项目经理同意;请假2天到4天的假还需要部门经理同意;请求4天到15天还需要总经理同意才行。
面向过程编程POP
/// <summary>
/// Program
/// </summary>
Application application = new Application()
{
Code = "Leave001",
Description = "请假单20220715",
Num = 16,
Type = "请假单",
IsApproval = false,
};
#region 面向过程编程POP
{
if (application.Num <= 8)
{
Console.WriteLine("主管批准");
}
else if (application.Num <= 16)
{
Console.WriteLine("项目经理批准");
}
else if (application.Num <= 32)
{
Console.WriteLine("部门经理批准");
}
else
{
Console.WriteLine("总经理批准");
}
}
#endregion
如果你的编程思想还是面向过程编程(POP)的思想,那么可能会写出上述代码。
但是我们要发挥C#面向对象编程(OOP)的特点,所以我们可以分别创建主管、项目经理、部门经理、总经理类,并在这些类中进行判断和流转。
面向对象编程OOP
首先,我们创建各个管理者类;
/// <summary>
/// 主管
/// </summary>
public class Director
{
public string Name { get; set; }
public void Approval(Application application)
{
Console.WriteLine($"{this.GetType().Name}-{this.Name} {nameof(Approval)}");
if (application.Num < 8)
{
Console.WriteLine("主管审批通过!");
application.IsApproval = true;
}
}
}
/// <summary>
/// 项目经理
/// </summary>
public class ProjectManager
{
public string Name { get; set; }
public void Approval(Application application)
{
Console.WriteLine($"{this.GetType().Name}-{this.Name} {nameof(Approval)}");
if (application.Num < 16)
{
Console.WriteLine("项目经理通过");
application.IsApproval = true;
}
}
}
/// <summary>
/// 部门经理
/// </summary>
public class DivisionManager
{
public string Name { get; set; }
public void Approval(Application application)
{
Console.WriteLine($"{this.GetType().Name}-{this.Name} {nameof(Approval)}");
if (application.Num < 32)
{
Console.WriteLine("部门经理审批通过");
application.IsApproval = true;
}
}
}
/// <summary>
/// 总经理
/// </summary>
public class President
{
public string Name { get; set; }
public override void Approval(Application application)
{
Console.WriteLine($"{this.GetType().Name}-{this.Name} {nameof(Approval)}");
Console.WriteLine("总经理审批通过");
application.IsApproval = true;
}
}
并通过Program类进行流转,
/// <summary>
/// Program
/// </summary>
#region 面向对象编程OOP
{
Director director = new Director();
director.Name = "Tom";
director.Approval(application);
if (!application.IsApproval)
{
ProjectManager projectManager = new ProjectManager();
projectManager.Name = "Bob";
projectManager.Approval(application);
if (!application.IsApproval)
{
DivisionManager divisionManager = new DivisionManager();
divisionManager.Name = "Mark";
divisionManager.Approval(application);
if (!application.IsApproval)
{
President president = new President();
president.Name = "Alice";
president.Approval(application);
}
}
}
}
#endregion
===============================================
Director-Tom Approval
ProjectManager-Bob Approval
DivisionManager-Mark Approval
部门经理审批通过
但是,这么写就把业务都暴露在了主方法内,我们需要把流转放入管理者类内部,使其自动流转。我们对各个管理者类修改如下,
审批自动流转
/// <summary>
/// 主管
/// </summary>
public class Director
{
public string Name { get; set; }
public void Approval(Application application)
{
Console.WriteLine($"{this.GetType().Name}-{this.Name} {nameof(Approval)}");
if (application.Num < 8)
{
Console.WriteLine("主管审批通过!");
application.IsApproval = true;
}
else
{
Console.WriteLine("我没有权限,请找上级审批!");
ProjectManager manager = new ProjectManager();
manager.Approval(application);
}
}
}
/// <summary>
/// 项目经理
/// </summary>
public class ProjectManager
{
public string Name { get; set; }
public void Approval(Application application)
{
Console.WriteLine($"{this.GetType().Name}-{this.Name} {nameof(Approval)}");
if (application.Num < 16)
{
Console.WriteLine("项目经理通过");
application.IsApproval = true;
}
else
{
Console.WriteLine("我没有权限,请找上级审批!");
DivisionManager manager = new DivisionManager();
manager.Approval(application);
}
}
}
/// <summary>
/// 部门经理
/// </summary>
public class DivisionManager
{
public string Name { get; set; }
public void Approval(Application application)
{
Console.WriteLine($"{this.GetType().Name}-{this.Name} {nameof(Approval)}");
if (application.Num < 32)
{
Console.WriteLine("部门经理审批通过");
application.IsApproval = true;
}
else
{
Console.WriteLine("我没有权限,请找上级审批!");
President manager = new President();
manager.Approval(application);
}
}
}
此时,Program类内部只需要如下代码,
/// <summary>
/// Program
/// </summary>
#region 审批自动流转
{
Director director = new Director();
director.Name = "Tom";
director.Approval(application);
}
#endregion
=====================================
Director-我是主管 Approval
我没有权限,请找上级审批!
ProjectManager- Approval
我没有权限,请找上级审批!
DivisionManager- Approval
部门经理审批通过
我们可以看出各个管理者类有着很多重复的代码,此时我们就可以把重复的代码抽离出来,放到一个抽象类里面。
抽象管理者类
/// <summary>
/// 抽象管理者类
/// </summary>
public abstract class AbstractManager
{
public string Name { get;set;}
public abstract void Approval(Application application);
}
/// <summary>
/// 主管
/// </summary>
public class Director : AbstractManager
{
public override void Approval(Application application)
{
Console.WriteLine($"{this.GetType().Name}-{this.Name} {nameof(Approval)}");
if (application.Num < 8)
{
Console.WriteLine("主管审批通过!");
application.IsApproval = true;
}
else
{
Console.WriteLine("我没有权限,请找上级审批!");
ProjectManager manager = new ProjectManager();
manager.Approval(application);
}
}
}
......
责任链模式精髓
进一步地,当我们要在项目经理和部门经理类直接加一个流转,或者要把项目经理的流转放在主管前面时,我们就需要去修改各个管理者类的代码,这也就违背了开闭原则。所以,我们这里要使得流转的过程能够控制,另一位开发同事可以自主去修改流转。具体代码如下,
public abstract class AbstractManager
{
public string Name { get;set;}
protected AbstractManager _abstractManager = null;
public void SetNext(AbstractManager abstractManager)
{
this._abstractManager = abstractManager;
}
public abstract void Approval(Application application);
protected void ApprovalNext(Application application)
{
Console.WriteLine("我没有权限,请找上级审批!");
if (this._abstractManager != null)
{
this._abstractManager.Approval(application);
}
}
}
/// <summary>
/// 主管
/// </summary>
public class Director : AbstractManager
{
public override void Approval(Application application)
{
Console.WriteLine($"{this.GetType().Name}-{this.Name} {nameof(Approval)}");
if (application.Num < 8)
{
Console.WriteLine("主管审批通过!");
application.IsApproval = true;
}
else
{
base.ApprovalNext(application);
}
}
}
/// <summary>
/// 项目经理
/// </summary>
public class ProjectManager : AbstractManager
{
public override void Approval(Application application)
{
Console.WriteLine($"{this.GetType().Name}-{this.Name} {nameof(Approval)}");
if (application.Num < 16)
{
Console.WriteLine("项目经理通过");
application.IsApproval = true;
}
else
{
base.ApprovalNext(application);
}
}
}
/// <summary>
/// 部门经理
/// </summary>
public class DivisionManager:AbstractManager
{
public override void Approval(Application application)
{
Console.WriteLine($"{this.GetType().Name}-{this.Name} {nameof(Approval)}");
if (application.Num < 32)
{
Console.WriteLine("部门经理审批通过");
application.IsApproval = true;
}
else
{
base.ApprovalNext(application);
}
}
}
/// <summary>
/// Program
/// </summary>
#region 审批流程变化(责任链模式精髓)
{
AbstractManager manager1 = new Director();
manager1.Name = "Tom";
AbstractManager manager2 = new ProjectManager();
manager2.Name = "Bob";
AbstractManager manager3 = new DivisionManager();
manager3.Name = "Mark";
AbstractManager manager4 = new President();
manager4.Name = "Alice";
manager1.SetNext(manager3);
manager3.SetNext(manager4);
manager1.Approval(application);
}
#endregion
==============================================================
Director-Tom Approval
我没有权限,请找上级审批!
DivisionManager-Mark Approval
部门经理审批通过
上述Program部分代码可以看出,我们能够将主管的流转改为部门经理。如果我们想要在项目经理之后加上大项目经理,我们也主要添加一个大项目经理类,然后在配置项目经理SetNext流转到大项目经理,不用去修改其他管理者类。
责任链模式+建造者模式
当然,我们如果不希望把业务流转的过程暴露在Program类中,我们可以引入建造者模型,在一个builder类中专门创建管理者类,并管理他们的流转方式。
public class ManagerBuilder
{
public static AbstractManager Build()
{
AbstractManager manager1 = new Director();
manager1.Name = "Tom";
AbstractManager manager2 = new ProjectManager();
manager2.Name = "Bob";
AbstractManager manager3 = new DivisionManager();
manager3.Name = "Mark";
AbstractManager manager4 = new President();
manager4.Name = "Alice";
manager1.SetNext(manager3);
manager3.SetNext(manager4);
return manager1;
}
}
/// <summary>
/// Program
/// </summary>
#region 责任链模式+建造者模式
{
AbstractManager manager = ManagerBuilder.Build();
manager.Approval(application);
}
#endregion
==============================================================
Director-Tom Approval
我没有权限,请找上级审批!
DivisionManager-Mark Approval
部门经理审批通过
3、优缺点
优点
- 降低了对象之间的耦合度
该模式降低了请求发送者和接收者的耦合度。
- 增强了系统的可扩展性
可以根据需要增加新的请求处理类,满足开闭原则。
- 增强了给对象指派职责的灵活性
当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。
- 责任链简化了对象之间的连接
一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
- 责任分担
每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。