设计模式(9) ------------职责链模式

前言

之前我们已经将前两种类型的设计模式都已经悉数介绍完呢(创建型和结构型),希望此时大家能够对它们有一个全新的认识,不仅加深对各个模式的理论理解,更需要在日常开发活动中实践之,这样才能真正做到对模式的融会贯通。模式的学习和领悟需要一个过程,不可能一蹴而就,更不可高开低走,需要大家静下心来扎实地一步步学习和提高。从今天开始,我们将继续完成最后一种类型的设计模式——行为型模式的介绍和学习,所谓的行为型模式实际上涉及到算法和对象间职责的分配问题,行为型模式不仅是描述对象或者类的模式,还描述它们之间的通信模式。这些模式扁鹊了在运行时刻难以跟踪的复杂控制流,将你的注意力从控制流转移到对象间的联系方式上来。同时行为型模式可分为行为类模式和行为对象模式两种,前者是使用继承机制在类间分派行为,后者是使用对象组合的方式来完成行为的分派。本文将对第一个行为型模式——职责链模式进行深入的介绍和学习,开始我们对行为型模式的探索之旅吧!

动机

在实际的软件系统开发中,有时我们会面临需要将操作请求转发到一系列接收者对象组成的链式结构情况,之所以是一系列接收者而不是单个接收者是因为操作请求者并不知道清楚当前请求将交由具体某个接收者来完成,而每个接收者能够处理的请求操作是有限度的,不可能面面俱到。面对一特定操作请求,它要么能够完全处理,完成对操作请求的相关任务,要么就只能向下传递给它认为可以胜任当前操作请求的下一个请求接收者对象,由其继续完成当前操作请求任务。试想一下,如果我们事先不能构建这样一条接收请求对象链,那么在客户端我们将不得不直接与处理特定请求的接收者打交道,这不仅增加了用户程序对请求接收者的使用难度,而且也势必造成两者的紧耦合关系。上述场景便是职责链模式擅长之地,在职责链模式里,若干对象由每一个对象对其下家的引用而连接起来形成请求处理链,请求将从该链头依次向链尾传递,直到遇到能够完全处理当前请求的接收者对象,此时才会结束请求向下传递过程。而请求的发出者并不知道当前链中哪个对象能够处理当前请求,这样系统就可以在不影响客户端的情况下动态地重新组织和分配职责,从而也就将具体操作请求与系列请求接收者解耦开来。接下来,让我们深入地学习职责链模式,尽量做到了然于胸吧。

意图

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

结构图


抽象处理者(Handler)角色:定义职责的接口,也就是在这里定义处理请求的方法,亦可以在这里实现后继链。

具体处理者(ConcreteHandler)角色:实现职责的类,在这个类中,实现对在它职责范围内请求的处理,如果处理不了,就继续转发请求给后继者。

客户端(client)角色:职责链的客户端,向链上的具体处理驍提交请求,让职责链负责处理。

代码示例


   public abstract class Handler{
       protected Handler successor;
       public void SetSuccessor(Handler handler){
           this.successor=successor;
       }
    
       public abstract void handleRequest(String request);
   }
    
  public class ConcreteHandler1 extends Handler{
      public void handleRequest(String request){
   
          //需要某些条件来判断当前请求是否为自己所能处理的范围,这里只是简单说明下
          boolean someCondition=false;
          if(someCondition){
              System.out.println("ConcreteHandler1  handle request"+request);
          }else {
              if(successor!=null){
                  //既然当前处理器不能处理请求,那么就直接将当前请求传递给它直接后继处理器,由其继续处理
                  successor.handleRequest(request);
              }
          }
      }
  }
   
  public class ConcreteHandler2 extends Handler{
      public void handleRequest(String request){
          boolean someCondition=false;
          if(someCondition){
              System.out.println("ConcreteHandler2  handle request"+request);
          }else {
              if(successor!=null){
                  successor.handleRequest(request);
              }else {
                  System.out.println("当前请求不能被所有处理者处理!");
              }
          }
      }
  }
   
  public class Client{
      public static void main(String[] args){
          ConcreteHandler1 handler1=new ConcreteHandler1();
          ConcreteHandler2 handler2=new ConcreteHandler2();
   
          handler1.SetSuccessor(handler2);
          handler2.SetSuccessor(null);
   
          handler1.handleRequest("just for test");            
      }
  }  


从示例代码中,我们可以大致清楚地明白职责链模式的实现方式,如果大家还记得之前我们介绍过的设计模式,应该对这段代码会感觉似曾相识,还记得装饰模式的示例程序吗?首先,我们通过定义完成职责的统一接口,即Handler类,在其中,我们不仅定义了完成职责的接口,而且也保存着其后继处理器引用句柄,目的就是当当前处理器无法处理当前请求操作时,交给这个后继处理器来处理,完成对请求传递过程,而不是停止于自己,让有能力负责的处理器来完成对请求的处理工作。而具体的处理器ConcreteHandler1和ConcreteHandler2都是实现了自己处理请求的业务逻辑功能,也就是hanlerRequest方法。而在客户端,我们根据业务需求将不同的具体处理器形成链式结构,然后直接将操作请求放置到处理链上进行处理即可,这样一来,客户端只需要知道职责链的第一个处理对象即可,因为需要通过它将操作请求传递到职责链中。当然,客户端也并不知道当前请求将由哪一个具体处理器接受处理,理想情况是不需要知道的。在这里,需要提醒一点是,虽然职责链在示例代码中是由客户端直接构建生成的,但是也完全可以由不同的情景上下文对象根据实际的业务要求来生成不同处理能力的职责链,直接交由客户端使用,而不需要客户端负责对职责链的创建工作。

在实际的职责链模式实现里,请求不一定会被处理,因为可能没有合适的处理者,请求在职责链中从头传到尾,每个处理对象判断不属于自己处理,最后请求就没有对象来处理,这一点需要明白。再者就是,在职责链模式里有纯与不不纯之说,所谓“纯”职责链模式,就是对于链上的每一个处理对象,要么能有能力直接处理请求,结束请求在职责链上的传递,要么就直接将请求原封不动地直接传递其后继处理对象处理,而不能既对请求做部分操作又将部分处理过的请求传递给后继处理对象。但是话虽如此,在现实的应用场景中,很难出现如此“纯”的职责链模式,所以还是只能看到“不纯”的职责链实现。

现实场景

在现实的生活场景中,其实也不乏职责链模式原型的例子。比如企业里的费用报销活动就是一个比较鲜活的实例,一般来说,对于较小金额的费用报销通过自己直系领导就可以获得批准报销,但是当报销费用金额变大时,直系领导就无权做决定呢,需要将该报销请求传递到其直系领导上呢,就这样,随着报销费用金额的不断加大,报销请求也就必将会依次传递到具有对当前报销费用做决定的领导身上。当然,在这一链上的任一领导都有权利直接否决该报销请求,也就是不将该报销请求继续往其直系领导传递呢,这也是真实场景可以发生的事情。但是不管最终哪一层级的领导有能力处理当前报销请求,作为报销人一开始只能将该报销请求传递给其直系领导,然后由他来决定是否需要将请求传递到更加层级的领导中,否则就是越级上报呢,现实中一般是不允许出现这样的操作流程的:)。说到这里,结合我们刚刚对职责链模式的介绍和示例代码,大家应该能够将上述场景出现的人、物与职责链模式中出现的角色一一对应了。首先,报销请求者就是client,而报销的费用就是request,而各个层级的领导就是各个具体的hanler对象,而企业中规定好各个层级领导所应具有的职权范畴,同时各个层级领导之间的上下级关系也是早已规定确定下来,也就是说自然形成了一个无形的职责链。下面我们就通过代码来演绎下上述场景吧!

   public abstract class Leader{
       protected Leader successor;
       public void SetSuccessor(Leader handler){
           this.successor=successor;
       }
    
       public abstract void handleRequest(long fee);
   }
    
  public class Manager extends Leader{
      public void handleRequest(long fee){
          if(fee<1000){
              //当报销费用小于一千时,经理有权力来决定批准不批准,而无需上报上级领导
              //下面只是简单示意一下,现在情况下是可以批或者不批:)
              System.out.println("批准或者不批准当前报销费用"+fee);
          }else {
              if(successor!=null){
                  //如果当前报销费用金额已经超过经理能够处理的范围应该直接将该报销请求传递到后继领导进行处理,
                  //在这里,也就是总经理呢。
                  successor.handleRequest(fee);
              }
          }
      }
  }
   
  public class President extends Leader{
      public void handleRequest(long fee){
          if(fee<10000){
              //当报销费用小于一万时,总经理有权力来决定批准不批准
              //下面只是简单示意一下,现在情况下是可以批或者不批:)
              System.out.println("批准或者不批准当前报销费用"+fee);
          }else {
              System.out.println("超过公司报销费用上限,直接否决:)");
              }
          }
      }
  }
   
  public class Client{
      public static void main(String[] args){
          Manager manager=new Manager();    
          President president=new President();
   
          manager.SetSuccessor(president);
          president.SetSuccessor(null);
   
          manager.handleRequest(500);
          manager.handleRequest(5000);        
      }
  }  
通过结构图来表示如下:

由经理和总经理构成的链式结构简单表示如下:

通过上图能直观地说明了经理是职责链的入口点,而总经理是职责链的最后处理者,如果它都无法处理,那该请求也只能原样返回或者直接废弃呢。但是对费用报销者来说,他并不清楚当前报销费用请求最终将被哪一层级领导处理,因为他本身并不需要知道哪一层级领导具有什么职能,并且现实情况下,他也只能将报销请求交由其直系领导来处理,而不能越级上报:)

另外,熟悉java web开发的朋友对jsp 中的过滤器Filter肯定不会陌生,我们可以定义多个不同功能的过滤器,在web.xml文件中配置和确定各个过滤器的执行过滤操作的先后顺序,形成一个过滤链。这样,从前台传递到后台的请求都必须依次通过当前过滤器链上的各个过滤器的过滤功能呢,最后请求才能进入到servlet中进行处理。从这点来说,过滤器链就是职责链模式的一种变形实现。

实现要点

  1. 实现后继者链:有两种方法可以实现后继者链,一个是定义新的链接,也就是通过Handler来定义,示例代码就是采用这种方式来完成;另一个是使用已有的链接,当已有的链接能够支持所需要的功能时,完全可以复用它们,而不需要额外重新定义新的职责链。
  2. 连接后继者:如有没有已有的引用可定义一个链,那么必须自己引入它们。这样,在Handler里,不仅要定义请求的接口,还需要维护后继链接,通过ConcreteHandler默认情况下直接将请求转发给后继者。
  3. 表示请求:一个请求可以是原始类型,亦可以是复杂的对象,具体情况需要根据实际情况作决定。

运用效果

  1. 降低耦合度:职责链模式便利一个对象无需知道是由哪个处理器对象来处理其请求,对象只需知道该请求会被“正解”处理。接收者和发送者都没有对方明确的信息,同时链中的对象无需知道链式结构。
  2. 增强了给对象指派职责的灵活性:可以通过在运行时刻对职责链进行动态的增加或者修改来增加或者改变处理一个请求的那些职责,也就是说可以对链中的任意处理对象进行修改,以满足实际需要。
  3. 不保证被接受:既然一个请求没有明确的接收者,也就不能保证请求一定会被处理,完全有可能请求到链的末端都得到不相应的处理对象的处理,当然我们可以对这种请求最终不能被处理情况定义一种默认的终结方式。
  4. 产生较多细粒度的对象:职责链会把功能处理分散到单独的职责对象中,也就是一个职责对象只会完成一种职责,在实现的业务处理过程中,需要很多的职责对象的组合,这样也就不可避免地会产生较多细粒度的职责对象。

适用性

  1. 如果有多个对象可以处理同一个请求,但是具体由哪个对象来处理该请求,是运行时刻动态决定的的。这种情况使用职责链模式,把处理请求的对象实现为职责对象,再将它们构成一个职责链,当请求在这个链中传递的时候,具体由哪个职责对象来处理,在运行时刻进行判断。
  2. 在不明确指定接收者的情况下,向多个对象中的其中一个提交请求。职责链模式将请求者与接收者之间解耦,请求者不需要知道究竟是哪一个接收者对象处理了请求。
  3. 需要动态地指定处理一个请求的对象集合时。职责链模式能动态地构建职责链,也就动态决定哪些职责对象参与到处理请求中来,这也就相当于动态地指定了处理一个请求的职责对象集合。

相关模式

  1. 职责链模式与组合模式:两者可以组合使用。可以把各个职责对象通过组合模式来组合,这样就可以通过组合对象自动递归地向上调用,由父组件作为子组件的后继,从而形成链结构。
  2. 职责链模式与装饰模式:两者本质相似,都需要在运行期间动态组合,装饰模式是动态组合装饰器,而职责链是动态组合处理请求的职责对象链。同时两者可以相互模拟实现对方的功能,装饰模式能够动态地给被装饰对象添加功能,要求装饰对象与被装饰对象拥有相同的接口,而职责链模式可以实现动态的职责组合,标准的功能是有一个接收者处理了请求就结束,但是职责对象完成本职责后不急于结束,而是继续往下传递请求的话,其功能与装饰模式的功能就差不多呢。当然,从目的上来说,两者还是具有很大不同的,装饰模式是要实现透明的为对象添加功能,而职责链模式是要实现发送者和接收者解耦,另外装饰模式可以无限递归调用,而职责链是有一个处理就结束。
  3. 职责链模式与策略模式:两者可以组合使用。可以在职责链械的某个职责实现的时候,使用策略模式来选择具体的实现,两样也可以在策略模式的某个策略袖中,使用职责链模式来实现功能处理,同理职责链械与可以与状态模式组合使用,详情说明将在后续文章中提及。

总结

职责模式的本质是:分离职责,动态组合。分离职责是前提,只有先把复杂的功能分开,拆分成各个小功能,然后才能合理规划和定义职责类;而动态组合才是职责链模式的精华所在,因为要实现请求对象和处理对象的解耦,请求对象并不知道最终的处理对象,所以需要动态地将可能的处理对象组合进来,也正因为组合是动态的,所以可以很方便地修改和增加亲的处理对象,从而使系统具有更好的灵活性和扩展性。另外,由于职责对象只完成一种职责,粒度较小,可以在多个不同功能的职责链中进行复用,增强职责功能的复用性。对职责链模式的介绍就至此为此吧,下一篇将继续讲述另一个行为型模式——命令模式,敬请期待!

参考资料

  1. 程杰著《大话设计模式》一书
  2. 陈臣等著《研磨设计模式》一书
  3. GOF著《设计模式》一书
  4. Terrylee .Net设计模式系列文章
  5. 吕震宇老师 设计模式系列文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值