乐趣区

关于设计模式:20-设计模式责任链模式

作为一个上班族,咱们可能会常常听到“治理流程凌乱”,“职责边界不清晰”等这样或那样的埋怨,这是当组织或零碎发展壮大后,一件事由一个人或者一个部门无奈独立实现时,不得不面对的问题。就拿平时销假来说,试想如果是一个只有几个人的小公司,很可能连请假条都不必写,间接跟老板说一下就 OK 了,然而如果公司有肯定规模,做为一个小小的螺丝钉,就未必有资格面见老板了,这时就不得不走审批流程了。咱们都晓得,通常状况下销假须要在 OA 上写请假单交由各级领导审批,不同级别的领导有权审批的天数不同,例如不超过 1 天的销假间接领导(TL)审批就能够了,而大于 1 天不超过 3 天就须要整个我的项目的负责人(PM)审批才能够,当然工夫更长的,如 7 天,则可能须要交由 CTO 甚至更高级的领导审批。

这个例子就曾经体现出责任的划分了,咱们先用代码模仿一下这个销假场景,而后由此引出咱们这次的配角 — 责任链模式。

示例

既然是销假,当然先得有请假单,而请假单又由申请和审批后果两局部组成,别离由销假人和审批领导填写,这不难理解,代码如下:

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);

    ...
}

乍一看,心里拔凉拔凉的,又变简单了,好不容易甩出去的锅又被甩回来了。不过呢,尽管有瑕疵,但绝对于最开始的实现,还是好了很多,至多,销假时只须要找间接领导就能够了,审批细节也不必再关注了,这就是责任链模式,上面看一下类图。

示例类图

定义

多个对象都有机会解决某个申请,将这些对象连成一条链,并沿着这条链传递该申请,直到解决实现为止。责任链模式的外围就是“链”,是由多个解决者串起来组成。

类图

  • Handler:形象解决者角色,是一个解决申请的接口或抽象类;
  • ConcreteHandler:具体的解决者角色,具体的解决者接管到申请后能够抉择将申请解决掉,或者将申请传递给下一个解决者。

责任链模式 + 模板办法模式

通过后面的代码和定义,咱们能够看到,责任链模式其实并不欠缺,首先管理者子类实现中有逻辑和代码上的反复,例如都须要判断是否有势力解决申请,解决完之后都须要交给下一个解决者解决,并且这个流程是固定的。因而,咱们能够进行如下革新,把公共固定的局部提取到基类中:

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 中的中间件就是典型例子,还有后面销假的例子,在有些公司,不论请多少天假,可能都须要所有领导逐级审批,所有领导都批准才算通过,只有有一个不批准就不通过,这仍然是责任链模式。

责任链模式应用起来能够非常灵活,实现形式也不止一种,但很少独自应用,更多时候还须要搭配其余模式一起应用,因而,要用好责任链模式也别忘了温习其它设计模式哦!

源码链接

退出移动版