做为一个上班族,咱们可能会常常听到“管理流程混乱”,“职责边界不清晰”等这样或那样的抱怨,这是当组织或系统发展壮大后,一件事由一我的或者一个部门没法独立完成时,不得不面对的问题。就拿平时请假来讲,试想若是是一个只有几我的的小公司,极可能连请假条都不用写,直接跟老板说一下就OK了,可是若是公司有必定规模,作为一个小小的螺丝钉,就未必有资格面见老板了,这时就不得不走审批流程了。咱们都知道,一般状况下请假须要在OA上写请假单交由各级领导审批,不一样级别的领导有权审批的天数不一样,例如不超过1天的请假直接领导(TL)审批就能够了,而大于1天不超过3天就须要整个项目的负责人(PM)审批才能够,固然时间更长的,如7天,则可能须要交由CTO甚至更高级的领导审批。git
这个例子就已经体现出责任的划分了,咱们先用代码模拟一下这个请假场景,而后由此引出咱们此次的主角---责任链模式。程序员
既然是请假,固然先得有请假单,而请假单又由申请和审批结果两部分组成,分别由请假人和审批领导填写,这不难理解,代码以下:github
public class LeaveContext { /// <summary> /// 申请 /// </summary> public LeaveRequest Request { get; set; } /// <summary> /// 审批结果 /// </summary> public LeaveResponse Response { get; set; } }
而后再来几个领导,每一个领导都有在必定范围内处理请求的能力。通过前面那么多设计模式的熏陶,针对不一样级别的领导抽象个管理者基类不难想到吧?算法
public abstract class Manager { public string Name { get; set; } public Manager(string name) { Name = name; } public abstract void HandleRequest(LeaveContext context); } /// <summary> /// 团队领导者 /// </summary> public class TL : Manager { public TL(string name) : base(name) { } public override void HandleRequest(LeaveContext context) { if (context.Request.LeaveDays <= 1) { context.Response = new LeaveResponse { Approver = "TL:" + Name, IsAgreed = true }; } } } /// <summary> /// 项目经理 /// </summary> public class PM : Manager { public PM(string name) : base(name) { } public override void HandleRequest(LeaveContext context) { if (context.Request.LeaveDays > 1 && context.Request.LeaveDays <= 3) { context.Response = new LeaveResponse { Approver = "PM:" + Name, IsAgreed = true }; } } } /// <summary> /// 首席技术官 /// </summary> public class CTO : Manager { public CTO(string name) : base(name) { } public override void HandleRequest(LeaveContext context) { if (context.Request.LeaveDays > 3 && context.Request.LeaveDays <= 7) { context.Response = new LeaveResponse { Approver = "CTO:" + Name, IsAgreed = true }; } } }
每一个领导都能对同一个请求进行处理,可是也各司其职,只作本身能力范围内的事,而且处理请求的角度和方式也大不相同(即代码实现上有较大的差别,这个例子过于简单,因此这点体现并不明显)。数据库
再来看看调用的地方:编程
static void Main(string[] args) { LeaveContext context = new LeaveContext { Request = new LeaveRequest { Applicant = "张三", Reason = "世界那么大,我想去看看", LeaveDays = new Random().Next(1, 10) } }; TL tL = new TL("李四"); PM pM = new PM("王五"); CTO cTO = new CTO("赵六"); if (context.Request.LeaveDays <= 1) { tL.HandleRequest(context); } else if (context.Request.LeaveDays <= 3) { pM.HandleRequest(context); } else if (context.Request.LeaveDays <= 7) { cTO.HandleRequest(context); } if (context.Response == null) { Console.WriteLine($"{context.Request.LeaveDays}天假期太长,没人处理请假申请,请假失败"); } else { Console.WriteLine($"{context.Response.Approver}审批了{context.Request.Applicant}的{context.Request.LeaveDays}天请假申请"); } }
上述代码有点多,但基本思路就是实例化请假单和若干领导对象,而后根据请假天数判断交给哪一个领导处理,最后再将处理结果打印输出。有兴趣的话,不妨下载源码,多运行几回看看结果,逻辑仍是至关严谨的。设计模式
不过,逻辑虽然严谨,但做为一名优雅的程序员,咱们不难挑出一些毛病,一方面,if...else
太多,扩展性很差;另外一方面,请假难度太大了,还容易出错,实际上,请假者只是想请个假而已,他也不知道谁有权处理,请个假总感受领导在相互甩锅,管理可不就混乱了吗?
不过对于这两个问题,咱们略微思索就会发现,前面遇到过,没错,就是状态模式,只不过状态模式是调用者不想关注系统内部状态的变化,而这里是不想关注内部审批流程的变化。状态模式的解决思路是将状态的设置转移到系统内部,即在一个具体状态类中处理完成对应的业务逻辑以后,设置下一个状态,这里不妨效仿一下。数组
public class TL : Manager { public TL(string name) : base(name) { } public override void HandleRequest(LeaveContext context) { if (context.Request.LeaveDays <= 1) { context.Response = new LeaveResponse { Approver = "TL:" + Name, IsAgreed = true }; return; } PM pM = new PM("王五"); pM.HandleRequest(context); } }
其余几个管理者对象相似处理,这样一来,调用者就简单了,代码以下:数据结构
static void Main(string[] args) { ... TL tL = new TL("李四"); tL.HandleRequest(context); ... }
不过,调用者虽然简单了,可是把锅甩给了管理者,不妨再看看上面的TL
类,不难看出面向实现编程了,违背了迪米特原则,进而也就违背了开闭原则,记得在状态模式中也一样有这个问题,咱们当时是经过享元模式解决的,缘由是状态是能够共享的,而且状态是系统内部的,外部不该该知道的。而这里状况有所不一样,管理者对象是不能够共享的,外部也是能够访问的,所以,处理手段也就不一样了。咱们在Manager
基类中添加NextManager
属性,这也是一种依赖注入的手段,以前的设计模式咱们用过了方法注入,构造函数注入,这是第三种注入方式---属性注入。框架
public abstract class Manager { public Manager NextManager { get; set; } ... }
而后具体的实现类中,经过NextManager
指向下一个管理者。下面以TL
类为例:
public class TL : Manager { ... public override void HandleRequest(LeaveContext context) { if (context.Request.LeaveDays <= 1) { context.Response = new LeaveResponse { Approver = "TL:" + Name, IsAgreed = true }; return; } NextManager?.HandleRequest(context); } }
这样全部管理者类就又面向抽象编程,能够轻松扩展了。咱们再来看看如何调用:
static void Main(string[] args) { ... TL tL = new TL("李四"); PM pM = new PM("王五"); CTO cTO = new CTO("赵六"); tL.NextManager = pM; pM.NextManager = cTO; tL.HandleRequest(context); ... }
乍一看,内心拔凉拔凉的,又变复杂了,好不容易甩出去的锅又被甩回来了。不过呢,虽然有瑕疵,但相对于最开始的实现,仍是好了不少,至少,请假时只须要找直接领导就能够了,审批细节也不用再关注了,这就是责任链模式,下面看一下类图。
多个对象都有机会处理某个请求,将这些对象连成一条链,并沿着这条链传递该请求,直处处理完成为止。责任链模式的核心就是“链”,是由多个处理者串起来组成。
经过前面的代码和定义,咱们能够看到,责任链模式其实并不完善,首先管理者子类实现中有逻辑和代码上的重复,例如都须要判断是否有权力处理请求,处理完以后都须要交给下一个处理者处理,而且这个流程是固定的。所以,咱们能够进行以下改造,把公共固定的部分提取到基类中:
public abstract class Manager { public Manager NextManager { get; set; } public string Name { get; set; } public Manager(string name) { Name = name; } public void HandleRequest(LeaveContext context) { if (CanHandle(context)) { Handle(context); return; } NextManager?.HandleRequest(context); } protected abstract bool CanHandle(LeaveContext context); protected abstract void Handle(LeaveContext context); }
上述代码将算法骨架封装到了HandleRequest(LeaveContext context)
方法中,而后将算法子步骤CanHandle(LeaveContext context)
和Handle(LeaveContext context
延时到子类中实现,固然,因为子步骤不该该被外部直接调用,所以访问修饰符为protected
,看到了吗?这是标准的模板方法模式。
再来看看具体子类如何实现,仍是以TL
为例:
public class TL : Manager { public TL(string name) : base(name) { } protected override bool CanHandle(LeaveContext context) { return context.Request.LeaveDays <= 1; } protected override void Handle(LeaveContext context) { context.Response = new LeaveResponse { Approver = "TL:" + Name, IsAgreed = true }; } }
子类更加干净清爽,职责也更加单一了,只需关心本身的处理逻辑,甚至都不用关心作完以后该交给谁,扩展起来更加容易了。
除此以外,另一个问题其实更加明显,就是前面说的锅甩来甩去仍是回到了调用者身上,虽然说调用者再也不须要知道每一个领导的审批权限范围,可是除了指定本身的领导,还得指定领导的领导,领导的领导的领导,这其实也不合理,出现这个问题的缘由是什么呢?缘由是不符合常理,咱们忽略的一个很重要的部门---人力行政部(HR),请假流程应该是他们提早制定好的,而不是每次请假时临时制定的。
所以,要解决这个问题,首先得加入一个HR
类,用于管理请假审批流程:
public class HR { public Manager GetManager() { TL tL = new TL("李四"); PM pM = new PM("王五"); CTO cTO = new CTO("赵六"); tL.NextManager = pM; pM.NextManager = cTO; return tL; } }
而后,再看看调用的地方:
static void Main(string[] args) { ... HR hR = new HR(); Manager manager = hR.GetManager(); manager.HandleRequest(context); ... }
又变得简单了,而且也更合理了。不过总体上仍是有点自欺欺人,由于新的HR
类又面向实现编程,变得难以维护了,所以,还得改进,改进方法仍是老套路,面向抽象编程,而后经过集合管理多个实例,具体代码以下:
public class HR { private List<Manager> _managers = new List<Manager>(); public void AddManager(Manager manager) { _managers.Add(manager); } public Manager GetManager() { Manager currentManager = null; for (int i = _managers.Count - 1; i >= 0; i--) { if (currentManager != null) { _managers[i].NextManager = currentManager; } currentManager = _managers[i]; } return currentManager; } }
这里直接一步到位了,可是应该能看懂,不过,你们有没有看出这是建造者模式呢?没看出也不要紧,咱们后面会再改进一次,毕竟HR
没面向抽象编程,光秃秃的看着也不舒服。但在此以前,咱们先看看调用的地方:
static void Main(string[] args) { ... HR hR = new HR(); hR.AddManager(new TL("李四")); hR.AddManager(new PM("王五")); hR.AddManager(new CTO("赵六")); Manager manager = hR.GetManager(); manager.HandleRequest(context); ... }
看到这里的朋友怕是要开骂了,这究竟是想干啥,封装来封装去,来来回回好几回,最后仍是回到了原点。但事实上,已经有了很大的不一样,第一次,调用者对业务逻辑有了较深的耦合,例如调用者必须知道每一个领导的审批权力;第二次,耦合度下降了,但仍是须要知道调用链的关系,而第三次,也就是这一次,其它的都不用知道,只须要建立对象就能够了,而建立对象是不管如何都绕不开的。而且,咱们通过一次次的改进,看似仍是回到了原点,但实际上已经将变化一步步从程序内部抛到了程序的最外层,能够经过依赖注入进一步解耦了,咱们不妨换成ASP.Net Core应用程序看看,同时咱们再进行最后一次改造:
public interface IManagerBuilder { void AddManager(Manager manager); Manager Build(); } public class ManagerBuilder: IManagerBuilder { private readonly List<Manager> _managers = new List<Manager>(); public void AddManager(Manager manager) { _managers.Add(manager); } public Manager Build() { ... } }
其实此次改造并无实质性的变化,仅仅是换了个名字而且加了个抽象的接口而已,目的是为了方便看出它确实是建造者模式。重点是如何使用它,先添加以下针对IServiceCollection
的扩展方法。
public static class ManagerServiceCollectionExtensions { public static IManagerBuilder AddManagers(this IServiceCollection services) { IManagerBuilder managerBuilder = new ManagerBuilder(); managerBuilder.AddManager(new TL("李四")); managerBuilder.AddManager(new PM("王五")); managerBuilder.AddManager(new CTO("赵六")); services.AddSingleton(managerBuilder); return managerBuilder; } }
而后在Startup
中调用,代码以下所示:
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddManagers(); }
好了,就是这么简单,接下来就能够在项目的任何地方经过依赖注入的方式使用了,固然,AddManagers(this IServiceCollection services)
还有瑕疵,可是,这能够经过配置文件或者读取数据库的方式解决,这里就再也不继续深刻下去了。
责任链模式最大的优势就是将请求和处理分离,请求者能够不用知道是谁处理的,处理者也能够不用知道请求的全貌,二者解耦,提升系统的灵活性。
既然责任链模式的核心是“链”,就说明每次请求都须要遍历整个链条,这必然会带来较大的性能损耗,不过事实上,责任链模式并不是必须使用链条,咱们知道数据结构中有数组和链表两种结构,而咱们前面刚学过的观察模式就和责任链模式就有相似的关系,观察者模式中经过集合保存全部的观察者,而后遍历集合,责任链模式也能够采用相同的手段,不过责任链模式采用集合保存全部处理者以后,或许就变成观察者模式了,可是这重要吗?
责任链模式采用了相似递归的方式,调试的时候逻辑可能比较复杂。
责任链模式一般能够用在含有流程的业务中,如工做流,流水线,请求流等,固然也能够将一个大的功能块切分红若干小块,而后经过责任链模式串联起来,责任链模式常见于各类框架中,是代码重构的利器,不过因为其性能不高,逻辑相对复杂,而且若是责任划分不清,很容易产生误用,带来的多是灾难,所以也须要慎重使用。何况,能经过责任链模式实现的场景每每也能够经过其它模式代替,如策略模式,状态模式,观察者模式等。另外,责任链模式的每一个处理者也能够只处理请求的一部分,ASP.Net Core中的中间件就是典型例子,还有前面请假的例子,在有些公司,无论请多少天假,可能都须要全部领导逐级审批,全部领导都赞成才算经过,只要有一个不一样意就不经过,这依然是责任链模式。
责任链模式使用起来能够很是灵活,实现方式也不止一种,但不多单独使用,更多时候还须要搭配其余模式一块儿使用,所以,要用好责任链模式也别忘了复习其它设计模式哦!