无废话C#设计模式之十七:Chain Of Resp.
意图
使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象能处理请求为止。
场景
假设我们在制作一个游戏的客服系统,客服有三种角色,分别是普通客服、客服经理和客服总监。玩家在网站中提问后,根据问题的分类和重要性处理的流程不一样。规则如下:
l 分类为1(游戏问题)和2(角色问题)的问题会由普通客服处理。
l 分类为3(充值问题)和4(账户问题)的问题会由客服经理处理。
l 普通客服和客服经理都没有能处理的问题由客服总监进行处理。
l 所有的问题都分为普通问题和重要问题,如果问题是重要问题则需要上一级的客服进行审核(普通客服回复的问题需要客服经理审核、客服经理回答的问题需要客服总监审核)。
这个处理的业务规则比较复杂,您可能会想到创建三个具体客服类型继承抽象客服,然后根据问题的性质传给不同的具体客服来处理。可是这样做有几个缺点:
l 客户端需要知道三个角色能处理的问题属性,三个角色能处理的问题应该只有三个角色自己清楚就可以了。
l 客户端需要和三个角色发生耦合,并且在一个处理过程中会先后调用不同的角色。普通客服在回复了重要问题后应该通知客服经理来审核,现在这个过程交给了客户端去做
客户端知道了太多客服处理问题的细节,对于玩家来说他只需要知道把这些问题告诉客服人员并且得到客服人员的处理结果,具体背后的处理流程是怎么样的它不用知道。由此,引入责任链模式来解决这些问题。
示例代码
using System; using System.Collections.Generic; using System.Text; namespace ChainOfRespExample { classProgram { staticList<CustomerService> gmTeam = newList<CustomerService>(); staticList<CustomerService> managerTeam = newList<CustomerService>(); staticList<CustomerService> directorTeam = newList<CustomerService>(); staticRandom random = newRandom(); staticvoid InitCOR() { if (managerTeam.Count > 0) { foreach (CustomerService cs in gmTeam) cs.SetLeader(managerTeam[random.Next(managerTeam.Count)]); } else { foreach (CustomerService cs in gmTeam) cs.SetLeader(directorTeam[random.Next(directorTeam.Count)]); } foreach (CustomerService cs in managerTeam) cs.SetLeader(directorTeam[random.Next(directorTeam.Count)]); // These configs above depends on business logic. } staticvoid InitGM() { for (int i = 1; i <= 9; i++) { CustomerService gm = newNormalGM("gm" + i); gm.SetResponsibleCaseCategory(newint[] { 1, 2 }); gmTeam.Add(gm); } for (int i = 1; i <= 2; i++) { CustomerService manager = newGMManager("manager" + i); manager.SetResponsibleCaseCategory(newint[] { 3, 4 }); managerTeam.Add(manager); } for (int i = 1; i <=1; i++) directorTeam.Add(newGMDirector("director" + i)); // These configs above should be from database. } staticvoid Main(string[] args) { InitGM(); InitCOR(); CustomerService gm = gmTeam[random.Next(gmTeam.Count)]; gm.HandleCase(newCase(1, false)); Console.WriteLine(Environment.NewLine); gm = gmTeam[random.Next(gmTeam.Count)]; gm.HandleCase(newCase(2, true)); Console.WriteLine(Environment.NewLine); gm = gmTeam[random.Next(gmTeam.Count)]; gm.HandleCase(newCase(3, false)); Console.WriteLine(Environment.NewLine); gm = gmTeam[random.Next(gmTeam.Count)]; gm.HandleCase(newCase(4, true)); Console.WriteLine(Environment.NewLine); gm = gmTeam[random.Next(gmTeam.Count)]; gm.HandleCase(newCase(5, true)); } } classCase { privateint caseCategory; publicint CaseCategory { get { return caseCategory; } } privatebool importantCase; publicbool ImportantCase { get { return importantCase; } } privatestring reply; publicstring Reply { get { return reply ; } set { reply = value; } } public Case(int caseCategory, bool importantCase) { this.caseCategory = caseCategory; this.importantCase = importantCase; } } abstractclassCustomerService { protectedCustomerService leader; protectedList<int> responsibleCaseCategory = newList<int>(); protectedstring name; publicstring Name { get { return name; } } public CustomerService(string name) { this.name = name; } publicvoid SetLeader(CustomerService leader) { this.leader = leader; } publicabstractvoid HandleCase(Case gameCase); publicvoid SetResponsibleCaseCategory(int[] responsibleCaseCategory) { foreach (int i in responsibleCaseCategory) this.responsibleCaseCategory.Add(i); } } classNormalGM : CustomerService { public NormalGM(string name) : base(name) { } publicoverridevoid HandleCase(Case gameCase) { if (responsibleCaseCategory.Contains(gameCase.CaseCategory)) { gameCase.Reply = "OK"; Console.WriteLine("The case has been replied by " + name); if (gameCase.ImportantCase) { Console.WriteLine("The reply should be checked."); leader.HandleCase(gameCase); } else Console.WriteLine("The reply has been sent to player."); } elseif (leader != null) { Console.WriteLine(string.Format("{0} reports this case to {1}.", name, leader.Name)); leader.HandleCase(gameCase); } } } classGMManager : CustomerService { public GMManager(string name) : base(name) { } publicoverridevoid HandleCase(Case gameCase) { if (responsibleCaseCategory.Contains(gameCase.CaseCategory)) { gameCase.Reply = "OK"; Console.WriteLine("The case has been replied by " + name); if (gameCase.ImportantCase) { Console.WriteLine("The reply should be checked."); leader.HandleCase(gameCase); } else Console.WriteLine("The reply has been sent to player."); } elseif (gameCase.Reply != null) { Console.WriteLine("The case has been checked by " + name); Console.WriteLine("The reply has been sent to player."); } elseif (leader != null) { Console.WriteLine(string.Format("{0} reports this case to {1}.", name, leader.Name)); leader.HandleCase(gameCase); } } } classGMDirector : CustomerService { public GMDirector(string name) : base(name) { } publicoverridevoid HandleCase(Case gameCase) { if (gameCase.Reply != null) { Console.WriteLine("The case has been checked by " + name); Console.WriteLine("The reply has been sent to player."); } else { gameCase.Reply = "OK"; Console.WriteLine("The case has been replied by " + name); Console.WriteLine("The reply has been sent to player."); } } } } |
代码执行结果如下图(后面一图为注释掉InitGM()方法中初始化managerTeam代码的执行结果):
代码说明
l Case类代表了问题。CaseCategory属性代表了问题的分类,普通客服和客服经理处理不同分类的问题。ImportantCase属性代表了问题是否是重要问题,如果是重要问题,则需要上级领导审核。Reply属性代表了客服对问题的回复。
l CustomerService类是责任链模式中的抽象处理者。我们看到,它定义了下个责任人的引用,并且提供了设置这个责任人的方法。当然,它也定义了统一的处理接口。
l NormalGM是具体处理者,它实现了处理接口。在这里,我们看到普通客服的处理逻辑是,如果这个问题的分类在它负责的分类之外则直接提交给上级领导进行处理(把对象通过责任链传递),否则就回复问题,回复问题之后看这个问题是否是重要问题,如果是重要问题则给上级领导进行审核,否则问题处理结束。
l GMManager也是一个具体处理者。客服经理处理问题的逻辑是,首先判断问题的问题是否在它负责的分类之内,如果是的话则进行处理(重要问题同样提交给上级处理),如果不是的话就看问题是否有了回复,如果有回复说明是要求审核的问题,审核后问题回复,如果问题还没有回复则提交给上级处理。
l GMDirector的处理流程就相对简单了,它并没有上级了,因此所有问题必须在它这里结束。对于没有回复的问题则进行回复,对于要求审核的问题则进行审核。
l 再来看看客户端的调用。首先,执行了InitGM()方法来初始化客服团队的数据,在这里我们的团队中有9个普通客服、2个客服经理和1个客服总监。普通客服只能回复分类1和分类2的问题,而客服经理只能回复分类3和分类4的问题。
l 然后,调用InitCOR()方法来初始化责任链,在这里我们并没有简单得设置普通客服的上级是客服经理、客服经理的上级是客服总监,而是自动根据是否有客服经理这个角色来动态调整责任链,也就是说如果没有客服经理的话,普通客服直接向客服总监汇报。
l 最后,我们模拟了一些问题数据进行处理。对于客户端(玩家)来说任何普通客服角色都是一样的,因此我们为所有问题随机分配了普通客服作为责任链的入口点。
l 首先来分析有客服经理时的处理情况。分类为1的问题直接由普通客服处理完毕。分类为2的重要问题由普通客服回复后再由客服经理进行审核。分类为3的问题直接由普通客服提交到客服经理进行处理。分类为4的重要问题也直接由普通客服提交到客服经理进行处理,客服经理回复后再提交到客服总监进行审核。分类为5的问题由普通客服提交到客服经理进行处理,客服经理再提交给客服总监进行处理。
l 再来分析没有客服经理时的处理情况。分类为1的问题直接由普通客服处理完毕。分类为2的重要问题由普通客服回复后再由客服总监进行审核。分类为3的问题直接由普通客服提交到客服总监进行处理。分类为4的重要问题也直接由普通客服提交到客服总监进行处理。分类为5的问题也直接由普通客服提交到客服总监进行处理。
l 如果没有责任链模式,这个处理流程将会多么混乱。
何时采用
l 从代码角度来说,如果一个逻辑的处理由不同责任对象完成,客户端希望能自定义这个处理流程并且不希望直接和多个责任对象发生耦合的时候可以考虑责任链模式。
l 从应用角度来说,如果对一个事情的处理存在一个流程,需要经历不同的责任点进行处理,并且这个流程比较复杂或只希望对外公开一个流程的入口点的话可以考虑责任链模式。其实,责任链模式还可以在构架的层次进行应用,比如.NET中的层次异常处理关系就可以看作是一种责任链模式。
实现要点
l 有一个抽象责任角色,避免各责任类型之间发生耦合。
l 抽象责任角色中定义了后继责任角色,并对外提供一个方法供客户端配置。
l 各具体责任类型根据待处理对象的状态结合自己的责任范围来判断是否能处理对象,如果不能处理提交给上级责任人处理(也就是纯的责任模式,要么自己处理要么提交给别人)。当然,也可以在进行部分处理后再提交给上级处理(也就是不纯的责任链模式)。
l 需要在客户端链接各个责任对象,如果链接的不恰当,可能会导致部分对象不能被任何一个责任对象进行处理。
注意事项
l 责任链模式和状态模式的区别在于,前者注重责任的传递,并且责任链由客户端进行配置,后者注重对象状态的转换,这个转换过程对客户端是透明的。