职责链模式,是属于行为型设计模式,可以把一个请求分给多个对象处理的机会,可以避免发送者和处理者之间的耦合。
(二)职责链模式的演变
现在员工需要请假,请假审批规则如下:
一天之内:组长审批通过即可,无需报送到部门经理;
一天到三天:部门经理审批即可,无效报送到中心老大;
三天到三十天:部门老大审批,无需报送到总裁办;
大于三十天:总裁办审批;
对于上面这个需求,开发者A拿到之后,马上写出了如下代码;
1 、定义一个请假的上下文
/// <summary> /// 请假的上下文 /// </summary> public class ApplyContext { public int Id { get; set; } /// <summary> /// 发起人 /// </summary> public string Name { get; set; } /// <summary> /// 请假天数 /// </summary> public int Day { get; set; } /// <summary> /// 请假描述 /// </summary> public string Description { get; set; } /// <summary> /// 审批结果 /// </summary> public bool AuditResult { get; set; } /// <summary> /// 审批备注 /// </summary> public string AudtiRemark { get; set; } }
2、根据需求,做了如下实现
var applyContext=new ApplyContext() { Id =1, Name ="小张", Day =4, Description ="老家有事" }; if (applyContext.Day <= 1) { Console.WriteLine("组长{0}审批....","groupleader"); applyContext.AuditResult = true; } if (applyContext.Day > 1 && applyContext.Day <= 3) { Console.WriteLine("部门经理{0}审批....", "groupleader"); applyContext.AuditResult = true; } //.....下面也是类似的,if-else这种做法。/ }
对于开发者A的做法,发现了这个基本上就是面向过程的编程,没有面向对象的思想(违法了单一职责原则),把所有东西都暴露到了上端。针对上面不好的地方,我们来改进我们的代码。
下面是开发者B的做法。
开发者B呢,就有一点面向对象的思想了,我们先把每个审批者提取出来,分别是组长,部门经理,中心老大,总裁。他们都有一个共同的操作,就是批假,再提取一个抽象出来(抽象类/接口),代码改进如下:
1、先抽取一个抽象出来,这里我们用抽象类
public abstract class AbstractAudtitor { /// <summary> /// 审批人的名字 /// </summary> public string Name { get; set; } /// <summary> /// 审批的方法 /// </summary> /// <param name="context"></param> public abstract void Audit(ApplyContext context); }
2、每个类,分别继承这个接口
public class CenterLeader:AbstractAudtitor { public override void Audit(ApplyContext context) { if (context.Day >3&&context.Day<=30) { Console.WriteLine($"{this.Name},中心领导审批"); context.AuditResult = true; } } }
上面代码,每个类都是类似的,在这里不一一列举;
3、调用过程
AbstractAudtitor groupLeader = new GroupLeader() { Name = "zhang", }; groupLeader.Audit(applyContext); if (!applyContext.AuditResult) { AbstractAudtitor deptManager = new DepartmentManager() { Name = "部门领导", }; deptManager.Audit(applyContext); if (!applyContext.AuditResult) { AbstractAudtitor centerLeader = new CenterLeader() { Name = "中心老大" }; centerLeader.Audit(applyContext); } else if (!applyContext.AuditResult) { AbstractAudtitor ceo = new CEO() { Name = "总裁办" }; ceo.Audit(applyContext); } else { Console.WriteLine("领导不批准!"); } }
从上面可以看出,虽然我们现在是面向对象了,但是我们只是简单的翻译了一下需求,完全没有站在实际情况中去考虑, 因为实际情况中,不可能是你一个普通员工,说给请假条给部门领导,中心老大,总裁办的,而是由各个层级之间不断的转发,换句话说,上端不应该去找各个环节,应该是各个层级之间互相转发。
那么接下来,开发者C根据刚刚所说,写了如下代码:
主要思路是:既然你说上端不要直接去找各个环节,那么我们就把这个找的动作,放到每一个环节类的后面,也就是做如下改变
public class CenterLeader : AbstractAudtitor { public override void Audit(ApplyContext context) { if (context.Day > 3 && context.Day <= 30) { Console.WriteLine($"{this.Name},中心领导审批"); context.AuditResult = true; } else { //在中心领导类里面,主动去找下一层,也就是总裁办的人。 new CEO() {Name = "zz"}.Audit(context); } } }
其他类,也是做了如下改变。确实我们按照上面这样做法,很满足我们的现在的需求,可能有一天领导说:这个请假流程得变更一下,或者请假审批的下一级,应该是可以动态指定的,流程是可以动态配置的。
为了满足这个需要,我们又要对上面的代码进行改进;
根据领导说:流程是要可以动态指定的,行,按照设计模式的原则,封装不变的,变化的就抛出去,我们大概做了如下改进:我们提供一个SetNext(),设置下一个环节的方法。代码如下:
因为每个环节都要提供一个SetNext方法,并且都需要一个AuditNext()的一个判断,所以我们就直接提供到父类AbstractAudtitor里面,具体代码如下:
1 先完善了刚才的父类。
public abstract class AbstractAudtitor { public string Name { get; set; } protected AbstractAudtitor _NextAuditor = null; /// <summary> /// 因为这个是父类,在这边写一个设置下一个环节的方法。 /// </summary> /// <param name="audtitor"></param> public void SetNext(AbstractAudtitor audtitor) { this._NextAuditor = audtitor; } public abstract void Audit(ApplyContext context); //下一个环节审批 protected void AuditNext(ApplyContext context) { if (this._NextAuditor != null) { this._NextAuditor.Audit(context); } else { context.AuditResult = false; context.AudtiRemark = "不允许请假!"; } } }
2 调用方式如下:
AbstractAudtitor groupLeader=new GroupLeader() { Name ="zz", }; AbstractAudtitor deptManager = new DepartmentManager() { Name = "经理", }; AbstractAudtitor centerLeader = new CenterLeader() { Name = "中心领导", }; AbstractAudtitor ceo = new CEO() { Name = "CEO", }; //设置下一个环节 groupLeader.SetNext(deptManager); deptManager.SetNext(centerLeader); centerLeader.SetNext(centerLeader); //上面就是设置流程 //发起流程 groupLeader.Audit(applyContext);
上面上端就可以对流程进行动态配置了,当然这里我们还需要用反射加配置文件的形式,把我们的流程做到更加可配置化。
(三)职责链模式的优缺点
优点:1 、请求者和处理者松耦合;2、职责明确,可动态配置。
缺点:1、产生了很多细粒对象;2、要注意链的有效性。