前言
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
一、职责链模式(Chain of Responsibility Pattern)
职责链模式属于行为型模式,它为请求创建了一个接收者对象的链。这种模式给予一个具体请求的类型,对请求的发送者和接收者进行解耦。
通常有 2 种方法来实现该模式。第 1 种是每个接收者都包含对下一个接收者的引用,以便在不能处理该请求时,转派请求至下一个接收者。第 2 个方法是引入中间链条类,由中间件负责转派请求。第一种方法的实现较为简单,本例采用第 2 种实现方式。
二、使用步骤
角色
1、抽象处理者(Handler)
定义出一个处理请求的接口。接口可以定义出一个方法以设定和返回对链条中下一个处理者的引用,如果使用第 2 种方式实现,可无需引用下一个处理者,统一由中间件负责;
2、具体处理者(Concrete Handler)
具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家;
3、请求类(Request)
处理者需要处理的请求信息;
4、中间链条类(Chain)
若使用第 2 种方式实现,则需要引入此中间类,内部维护所有处理者,并在需要时自动转派请求至下一个处理者。
示例
命名空间 ChainOfResponsibility 包含领导 Leader 类充当处理者基类,它包含 4 个实现类,经理类 Manager、总监类 Inspector、总经理类 President 和董事 Directorate 类,请假信息 LeaveRequest 类充当请求类,LeaderChain 类充当中间链条类。本案例尝试以员工请假来解释职责链模式在审批环节的应用。
public class LeaveRequest {
public int Days { get; set; }
public string Name { get; set; }
public LeaveRequest(int days, string name) {
Days = days;
Name = name;
}
}
复制代码
请假请求 LeaveRequest 类,包含需要请假的天数和员工的姓名。一个公开的构造函数表明调用方必须提供请假天数和员工姓名信息。
public abstract class Leader {
protected string Name { get; set; }
protected Leader(string name) {
this.Name = name;
}
public static LeaderChain Chain { protected get; set; }
public abstract void ProcessRequest(LeaveRequest request);
protected void Delivery(LeaveRequest request) {
Chain.DoChain(request);
}
}
复制代码
领导者 Leader 类,充当处理者基类,包含领导的姓名 Name 并维持对中间链的引用。ProcessRequest 为处理请假的抽象方法,为处理请假公开了一个调用接口。Delivery 则为在不能处理请求时转派至下一个处理者。
public class Manager : Leader {
public Manager(string name) : base(name) { }
public override void ProcessRequest(LeaveRequest request) {
if (request.Days <= 2) {
Console.WriteLine($"{this.Name} approved {request.Name}'s " +
$"leave request for {request.Days} days!");
return;
}
Delivery(request);
}
}
复制代码
具体处理者,经理 Manager 类,如果员工的请假天数小于等于 2 天,则经理有权限批准该请假请求。
public class Inspector : Leader {
public Inspector(string name) : base(name) { }
public override void ProcessRequest(LeaveRequest request) {
if (request.Days <= 4) {
Console.WriteLine($"{this.Name} approved {request.Name}'s " +
$"leave request for {request.Days} days!");
return;
}
Delivery(request);
}
}
复制代码
具体处理者,总监 Inspector 类,如果员工的请假天数小于等于 4 天,则总监有权限批准该请假请求。
public class President : Leader {
public President(string name) : base(name) { }
public override void ProcessRequest(LeaveRequest request) {
if (request.Days <= 8) {
Console.WriteLine($"{this.Name} approved {request.Name}'s " +
$"leave request for {request.Days} days!");
return;
}
Delivery(request);
}
}
复制代码
具体处理者,总经理 President 类,如果员工的请假天数小于等于 8 天,则总经理有权限批准该请假请求。
public class Directorate : Leader {
public Directorate(string name) : base(name) { }
public override void ProcessRequest(LeaveRequest request) {
if (request.Days > 8) {
Console.WriteLine($"{this.Name} approved {request.Name}'s " +
$"leave request for {request.Days} days!");
return;
}
Delivery(request);
}
}
复制代码
具体处理者,董事 Directorate 类,如果员工的请假天数大于 8 天,则需要董事会批准该请假请求。
public class LeaderChain {
private List<Leader> _leaders = new List<Leader>();
private int _cursor = 0;
public void Attach(Leader leader) {
if (leader == null) throw new ArgumentNullException();
_leaders.Add(leader);
}
public bool Detach(Leader leader) {
if (leader == null) throw new ArgumentNullException();
return _leaders.Remove(leader);
}
public void DoChain(LeaveRequest request) {
if (_cursor <= _leaders.Count - 2) {
_leaders[++_cursor].ProcessRequest(request);
}
_cursor = 0;
}
}
复制代码
中间链条类 LeaderChain,首先内部维持对所有处理者的引用,包含的游标_cursor 指示链条所处的位置,Attach 和 Detach 方法分别向链条中增加和删除处理者。而 DoChain 方法则真正转派请求至下一个处理者。
此处需要注意的是,由于请求信息是由第一个处理者直接调用的,所以初始游标位置为 0 并且在 DoChain 方法中使用++_cursor 作为处理者列表的索引参数。也就是说当第一次转派请求时,索引的值为 1(因为使用了++_cursor),即为链条中的第 2 个处理者。请各位看官仔细思考此处逻辑。
public class Program {
public static void Main(string[] args) {
var leaders = new List<Leader>{
new Manager("Tom"),
new Inspector("Juice"),
new President("Iori"),
new Directorate("Marin")
};
var chain = new LeaderChain();
foreach (var leader in leaders) {
chain.Attach(leader);
}
Leader.Chain = chain;
var requests = new List<LeaveRequest> {
new LeaveRequest(1, "Zhao"),
new LeaveRequest(3, "Qian"),
new LeaveRequest(5, "Sun"),
new LeaveRequest(7, "Li"),
new LeaveRequest(12, "Zhou")
};
foreach (var request in requests) {
leaders[0].ProcessRequest(request);
}
Console.ReadKey();
}
}
复制代码
以上为调用方代码的示例,首初始化一个处理者列表并增加至中间链条类,之后模拟“赵、钱、孙、李、周”5 位同学的请假请求,他们分别要请 1、3、5、7、12 天假,最后调用 ProcessRequest 处理请求。以下是这个案例的输出结果:
Tom approved Zhao's leave request for 1 days!
Juice approved Qian's leave request for 3 days!
Iori approved Sun's leave request for 5 days!
Iori approved Li's leave request for 7 days!
Marin approved Zhou's leave request for 12 days!
复制代码
<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">
总结
优点
1、降低耦合度,它将请求的发送者和接收者解耦;2、简化了对象,使得对象不需要知道链的结构;3、增强给对象指派职责的灵活性,通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任;4、增加新的请求处理者类很方便。
缺点
1、不能保证请求一定被接收;2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用;3、可能不容易观察运行时的特征,不利于程序的调试。
使用场景
1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时确定;2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求;3、需要动态指定一组对象处理请求。
评论