责任链(Chain of Responsibility)模式

 

   面向对象开发人员通常希望明确和减少对象间的责任,从而降低对象之间的耦合程序.这样我们的系统更加容易修改,同时也可降低产生缺陷的风险。从某种程序上说,Java语言本身能够帮助降低对象间的耦合度。客户端只能访问对象的可见接口,而无需关心对象的实现细节。借助于这种组织方式,客户端代码只需了解哪些类有哪些方法可调用即可;除此之外,客户端代码与被使用的代码无任何关联。如果我们将若干对象按照某种层次结构进行组织,比如组织为类责任链,客户端代码就有可能不必事先了解自己将使用哪一个类。在这种情况下,链中的每个对象都有一个方法,当客户端代码调用该方法的时候,这些对象要么执行该方法,要么沿着这条链转发该方法调用请求。

 

   Chain of Responsibility模式可让每个对象都有一次机会决定自己是否处理请求,以便于避免请求的发送者与其接收者之间的耦合

 

1.常见的责任链:

   正如组合模式中介绍的那样,Oozinoz把机器、生产线、车间、工厂模型化为“机器组件”。通过这种方法,操作可以简单地并以递归方式实现,比如关闭某车间中所有机器的操作,也可以简化对工厂中工程师职责的模拟。在Oozinoz公司中,始终有位工程师负责某个特定的机器组件,尽管这种职责也许是按照层次分配的。比如对于火药球填压机这样一台复杂的机器,也许会直接分配一个责任工程师。对于简单的机器,也许不会直接分配责任工程师,负责本机器所在生产线或者所在车间的工程师同时对这台机器负责。

   我们希望当客户对象查询责任工程师时,能够避免查询多个对象。为完成这个使命,我们可以使用Chain of Responsibility模式,分配给每个机器组件一个责任人对象。下图可以说明这种设计思路:


                                      

     每个Machine或者MachineComposite对象都有父对象(parent)

       和责任人对象(responsible),这些属性是从MachineComponent类继承的

 

    上图的设计思路允许(但不是强制要求)每个机器组件跟踪其责任工程师。如果某机器没有直接分配责任工程师,它可以把查询责任工程师的请求发送给其父类。一般来讲,机器的父类是生产线,生产线的父类是车间,车间的父类是工厂。在Oozinoz公司中,这个职责链始终有一位责任工程师。

    这种设计思路和好处是机器组件的客户代码不必了解责任工程师是如何分配的。客户代码可以询问任何机器组件或者其责任工程师。机器组件把客户代码与职责分配知识相互独立起来。当然在另一方面,这种设计思路也存在不合适的地方。

 

突破题:请指出上图中设计思路的两个明显弱点。

答:此设计思路(目的是查找机器的责任工程师)的一些潜在缺陷陈列如下:

(1) 我们没有指定责任链如何设置以便于机器知道其父对象。实际上,很难保证父对象始终不是null。

(2) 根据父对象设置方式,搜索父对象有可能陷入死循环,这一点是可以想象的。

(3) 并不是怕有对象都拥有这些新方法暗示的所有行为(比如顶层没有父项)。

(4) 当前设计局限于细节,关心系统如何知道哪些工程师在工厂,以及是否可用。该设计并不清楚完成责任需要的实际时间。

 

   Chain of Responsibility模式可以帮助我们简化客户端代码,尤其当客户端代码不清楚对象组中哪个对象负责处理查询请求时。如果事先没有建立Chain of Responsibility模式,代码会比较复杂,也许要借助其他方法来简化代码设计。

 

2.重构为Chain of Responsibility模式:

  如果发现客户端代码在发出调用请求之前首先进行调用探查时,也许就应该通过代码重构来改善代码设计了.为了决定是否使用Chain of Responsibility模式,需要事先明确类组中对象是否支持责任链。比如Oozinoz的机器组件有时提供对责任工程师的引用。把期望的操作添加到类组中的每个类,并用链策略来实现这个操作能够满足这个请求。

  下面让我们考虑Oozinoz代码库中对工具和工具车的建模。工具不属于MachineComponent类层次,但是在某些地方与机器类似。尤其是工具始终分配给工具图形,并且工具车有一个责任工程师这些方面。假设某可视化程序可以显示特定车间的所有工具和机器,并且提供弹出式信息以显示特定项的责任工程师。下图显示了在查询指定设备的责任工程师的过程中所涉及的类:



        某模拟环境下包含的设备项,包括各种机器、机器组合、工具以及工具车

   VisualizationItem接口指定类需要的部分行为,以便于实现可视化,同时不需要getResponsible()方法。实际上,并不是VisualizationItem的所有项都知道具体的责任工程师。当可视化程序需要决定哪个工程师对特定设备负责时,答案依赖于所选的是哪个设备项。机器、机器组和工具车都有getResponsible()方法,但是工具没有。为确定工具的责任工程师,程序代码必须查看这个工具属于哪个工具车,然后查看谁负责这个工具车。为确定指定设备项的责任工程师,应用程序的菜单代码使用一些if语句和类型判断。这些特性表明重构也许有助于改善代码,具体代码如下:

package ocm.oozinoz.machine;

public class AmbitiousMenu
{
          public Engineer getResponsible(VisualizationItem item)
          {
                if ( item instance of Tool)
                {
                       Tool t = (Tool)item;
                       return t.getToolCard().getResponsible();
                }

                 if ( item instance of ToolCart)
                {
                       ToolCart tc = (ToolCart)item;
                       return tc.getResponsible();
                }

                if ( item instance of MachineComponent)
                {
                       MachineComponent c = (MachineComponent)item;
                       if(c.getReponsible()!=null)
                         return c.getResponsible();
                       if(c.getParent() != null)
                         return c.getParent().getReponsible(); 
                }
                 return null;
          }
}

Chain of Responsibility模式的目的是减轻调用者的压力,使它们无需了解哪个对象可以处理调用请求。在本例中,调用者是个菜单,其请求是找到责任工程师。在当前设计中,调用者必须了解哪个设备项有getReponsible()方法。借助于Chain of Reponsiblity模式为所有模拟项提供责任人,我们可以借此来简化代码。这样菜单代码就不必了解哪些对象了解责任工程师相关的知识,而可以迁移到模拟项中。

 

突破题:我们可以将getReponsible()方法移入VisualizationItem接口,并将该方法加入Tool类。请据此重新绘制上面的类图。


                           

                                         每个VisualizationItem对象知道其责任工程师。从内部实现来讲,

                                                VisualizationItem对象也许把这个请求转发给其父对象

   通过这样的设计,客户代码可以借助责任链上的任何模拟对象来获取相关责任工程师的信息。另外,这个方法让客户不必掌握哪些对象知道职责,可以把负担全部转移给实现VisualizationItem接口的对象。

   现在,菜单代码可以直接通过调用VisualizationItem对象的方法来查找责任工程师,代码将会更简单:

package com.oozinoz.machine;

public class AmbitiousMenu2
{
     public Engineer getResponsible(VisualizationItem item)
     {
          return item.getResponsible();
     }
}

每个设备项的getResponsible()方法也会变得更加容易实现。

 

突破题:请给出下面的类的getResponsible()方法的实现:

A.MachineComponent

B.Tool

C.ToolCart

 

答:A.一个MachineComponent对象可能对应一名责任人。如果某个MachineComponent对象并不对应一名责任人,该对象将会把对getResponsible()方法的调用转发给其父类节点对象:

 

public Engineer getResponsible()
{
    if(responsible != null) 
       return responsible;
    if(parent != null)
       return parent.getReponsible();
    return null;
}

 

B.Tool.Responsible类根据“工具总是被指派给某个工具车”这样一个原则来实现getResponsible()方法:

public Engineer getResponsible()
{
    return toolCart.getResponsible();
}

C.ToolCart类根据“每个工具车都有一名责任工程师”这样一个原则来实现getResponsible()方法:

public Engineer getResponsible()
{
    return responsible;
}

 

3. 固定责任链

  当我们在为MachineComponent类编写getResponsible()方法的时候,必须考虑到MachineComponent对象的父对象可能为空。解决方法之一是,让每个MachineComponent对象都有一个非空对象。这样做可以让我们的对象模型更加紧凑。为实现这个目的,可以为MachineComponent类的构造器增加一个参数来提供父对象(如果所提供的父对象为空,甚至可以抛出一个异常,以便知道异常被捕获的位置)。另外,我们要考虑到位于根部的对象---该对象没有父对象。一种可行的做法是,创建一个MachineRoot类,并让该类继承MachineComposite类(而不是MachineComponent)。为了保证每个MachineComponent对象都对应一名责任工程师,可以这样做:

 (1)MachineRoot类的构造器需要一个Enginner对象;

 (2)MachineComponent类的构造器需要一个类型为MachineComponent的父对象;

 (3)只有MachineRoot使用null作为其父对象的值。

 

突破题:请在下图中写出各个类的构造器,从而保证每个MachineComponent对象都对应一名责任工程师。



     MachineComponent类层次结构的构造器支持两个准则:MachineRoot对象必须有一个责任工程师对象;除了根对象以外的每个MachineComponent对象都必须有一个父节点

 

我们为Machine类和MachineComposite类提供的构造器,应该保证无论是否配备有责任工程师,两个类都能被初始化。如果某个MachineComponent对象没有指派一名责任工程师,那么就从其父节点对象那里获取责任工程师。 

 

  通过固定责任链,我们的对象模型变得更加健壮,其代码也愈加简化。现在就可以把MachineComponent类的getResponsible()方法实现如下:

public Engineer getResponsible()
{
    if(responsible!=null)
      return responsible;
    return parent.getResponsible();
}

 

12.4 不带组合结构的Chain of Responsibility模式 

    在使用Chain of Responsiblity模式的时候,我们需要确定一个查询策略来指定查找处理请求对象的查询顺序。通常,采用哪种查询策略取决于建模领域的背景知识。如果对象模型中存在某些类型的组合,如Oozinoz公司的机器类层次结构,那么就会经常出现这种情况。不过Chain of Responsibility模式也可以用于不带组合结构的对象模型中。

 

突破题:请举出一个例子,说明Chinn of Responsibility模式可应用于不带组合结构的对象链模型中。

答:(1)一群接线工程师按照标准的循环顺序轮流为客户服务。这些接线工程师形成了一个环状的链。如果当前首席工程师在一定的时间内回答不了有关产品支持的问题,通知系统则将切换到下一位工程师,请他来回答这个问题。

     (2)当用户在输入事件日期等信息的时候,一些解析器会轮渡来解析用户输入的文本,这些解析器也形成一个链。

 

5.小结

  当应用Chain of Responsibility模式后,客户端代码不必事先知道对象集合中哪个对象可提供自己所需要的服务。当客户端代码发出调用请求之后,该请求沿着责任链转发,直到找到提供该服务的对象为止。这样可大大降低客户端代码与提供服务的对象之间的耦合程度。

  如果某个对象链能够应用一系列不同的策略来解决某个问题,如解析用户的输入,那么这种情况也可以应用Chain of Responsibility模式。该模式更常见于组合结构;该结构具有一个包容的层次结构,它为对象链提供一种自然的查询顺序。简化对象链和客户端的代码是Chain of Responsibility模式的一个主要优点。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值