问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

在组合模式中实现访问者(Visitor)模式

发布网友 发布时间:2023-01-19 12:26

我来回答

1个回答

热心网友 时间:2023-12-16 09:31

  本文从一个给定的实现了组合(Composite)模式的例子开始 说明怎么在这个数据结构上实现业务逻辑代码 依次介绍了非面向对象的方式 在组合结构中加入方法 使用访问者(Visitor)模式以及用改进后的访问者(Visitor)模式来实现相同的业务逻辑代码 并且对于每种实现分别给出了优缺点

  读者定位于具有Java程序开发和设计模式经验的开发人员

  读者通过本文可以学到如何在组合(Composite)模式中实现各种不同的业务方法及其优缺点

  组合(Composite)模式

  组合模式是结构型模式中的一种 GOF的《设计模式》一书中对使用组合模式的意图描述如下 将对象组合成树形结构以表示 部分 整体 的层次结构 Composite使得用户对单个对象和组合对象的使用具有一致性

  组合模式应用广泛 根据GOF中对组合模式的定义 Composite模式一般由Component接口 Leaf类和Composite类组成 现在需要对一个软件产品管理系统的实体建模 某公司开发了一系列软件集(SofareSet) 包含了多种品牌(Brand)的软件产品 就象IBM提供了Lotus WebsPhere等品牌 每个品牌下面又有各种产品(Proct) 如IBM的Lotus下面有Domino Server/Client产品等 建模后的类图如下(代码可以参见随本文带的附件中 包 test entity下所有的源文件)

  

  如图所示

  ( )接口SofareComponent就是对应于组合模式中的Component接口 它定义了所有类共有接口的缺省行为

  ( )AbsSofareComposite类对应于Composite类 并且是抽象类 所有可以包含子节点的类都扩展这个类 这个类的主要功能是用来存储子部件 实现了接口中的方法 部分可以重用的代码写在此类中

  ( )SofareSet类继承于AbsSofareComposite类 对应于软件集 软件集下直接可以包含品牌(Brand) 也可以直接包含不属于任何品牌的产品(Proct)

  ( )Brand类继承于AbsSofareComposite类 对应于品牌 包含了品牌名属性 并且用来存储Proct类的实例

  ( )Proct类就是对应的Leaf类 表示叶子节点 叶子节点没有子节点

  用不同的方法实现业务逻辑

  数据结构建立好之后 需要在这个数据结构上添加方法实现业务逻辑 比如现在的这个例子中 有这样的需求 给定一些用户选择好的产品 需要计算出这些选中后软件的总价格 下面开始介绍如何使用各种不同的方法来实现这个业务逻辑

  非面向对象的编程方式

  这种方式下 编程思路最简单 遍历SofareSet实例中的所有节点 如果遍历到的当前对象是Proct的话就累加 否则继续遍历下一层直到全部遍历完毕 代码片断如下

/**  * 取得某个SofareComponent对象下面所有Proct的价格  * @param brand  * @return  */ public   double  getTotalPrice(SofareComponent sofareComponent) {     SofareComponent temp = sofareComponent;      double  totalPrice =  ;      //如果传入的实例是SofareSet的类型      if  (temp  instanceof  SofareSet) {         Iterator it = ((SofareSet) sofareComponent) getChilds()                &erator();          while  (it hasNext()) { //遍历             temp = (SofareComponent) it next();              //如果子对象是Proct类型的 直接累加              if  (temp  instanceof  Proct) {                 Proct proct = (Proct) temp;                 totalPrice += proct getPrice();             }  else   if  (temp  instanceof  Brand) {               //如果子对象是Brand类型的 则遍历Brand下面所有的产品并累加                 Brand brand = (Brand) temp;                 totalPrice += getBrandPrice(brand);             }         }     }  else   if  (temp  instanceof  Brand) {          //如果传入的实例是SofareSet的类型 则遍历Brand下面所有的产品并累加         totalPrice += getBrandPrice((Brand) temp);     }  else   if  (temp  instanceof  Proct) {          //如果子对象是Proct类型的 直接返回价格          return  ((Proct) temp) getPrice();     }      return  totalPrice; } /**  * 取得某个Brand对象下面所有Proct的价格  * @param brand  * @return  */ private   double  getBrandPrice(Brand brand) {     Iterator brandIt = brand getChilds(erator();      double  totalPrice =  ;      while  (brandIt hasNext()) {         Proct proct = (Proct) brandIt next();         totalPrice += proct getPrice();     }      return  totalPrice; }

  这段代码的好处是实现业务逻辑的时候无需对前面已经定好的数据结构做改动 并且效率比较高 缺点是代码凌乱而且频繁使用了instanceof判断类型和强制类型转换 代码的可读性不强 如果层次多了代码就更加混乱

  面向对象的编程方式(将计算价格的方法加入数据结构中)

  下面我们采用面向对象的方式 可以这么做 在接口SoftWareComponent中加入一个方法 名叫getTotalPrice 方法的声明如下

/**  * 返回该节点中所有子节点对象的价格之和  * @return  */ public   double  getTotalPrice();   由于类Brand和SofareSet都继承了AbsSofareComposite 我们只需在类AbsSofareComposite中实现该方法getTotalPrice方法即可 如下 public   double  getTotalPrice() {     Iterator it = erator();      double  price =  ;      while  (it hasNext()) {         SofareComponent sofareComponent = (SofareComponent) it next();                         //自动递归调用各个对象的getTotalPrice方法并累加         price += sofareComponent getTotalPrice();     }      return  price; }   在Proct类中实现如下 public   double  getTotalPrice(){      return  price; }   在外面需要取得某个对象的总价格的时候只需这样写(在本文的例子 test business SofareManager中可以找到这段代码) // getMockData()方法返回数据 SofareComponent data = getMockData(); //只需直接调用data对象的getTotalPrice 方法就可以返回该对象下所有proct对象的价格 double  price = data  getTotalPrice(); //找到某个对象后直接调用其getTotalPrice方法也可以返回总价格 price = data  findSofareComponentByID( id ) getTotalPrice();

  现在把业务逻辑的实现都放在了数据结构中(组合模式的结构中) 好处很明显 每个类只管理自己相关的业务代码的实现 跟前面举的面向过程方式的实现方式相比 没有了instanceof和强制类型转换 但是不好的地方是如果需要增加新的业务方法的话就很麻烦 必须在接口SoftWareComponent中首先声明该方法 然后在各个子类中实现并且重新编译

  使用访问者模式

  使用访问者模式就能解决上面提到的问题 如果要经常增加或者删除业务功能方法的话 需要频繁地对程序进行重新实现和编译 根据面向对象设计原则之一的SRP(单一职责原则)原则 如果一个类承担了多于一个的职责 那么引起该类变化的原因就会有多个 就会导致脆弱的设计 在发生变化时 原有的设计可能会遭到意想不到的破坏 下面我们引入了一个叫做Visitor的接口 该接口中定义了针对各个子类的访问方法 如下所示

public   interface  Visitor {      public   void  visitBrand(Brand brand);      public   void  visitSofareSet(SofareSet sofareSet);      public   void  visitProct(Proct proct); }   visitBrand方法是访问Brand对象节点的时候用的 剩下的方法依次类推 并在接口SofareComponent中增加一个方法 public   void  accept(Visitor visitor);   在SofareSet中实现接口中的accept方法 首先直接调用Visitor接口中的visitSofareSet方法 传入的参数是本身对象 然后递归调用子对象的accept方法 public   void  accept(Visitor visitor) {     visitor visitSofareSet( this );     Iterator it = erator();      while  (it hasNext()) {         SofareComponent ponent = (SofareComponent)it next();         ponent accept(visitor);     } }    在Brand中实现接口中的accept方法 首先直接调用Visitor接口中的visitBrand方法 传入的参数是本身对象 然后递归调用子对象的accept方法 public   void  accept(Visitor visitor) {     visitor visitBrand( this );     Iterator it = erator();      while  (it hasNext()) {         SofareComponent ponent = (SofareComponent)it next();         ponent accept(visitor);     } }    其实在上面的两个类的实现中可以将遍历子节点并调用其accept方法的代码写到父类AbsSofareComposite中的某个方法中 然后直接调用父类中的这个方法即可 这里为了解释方便分别写在了两个子类中     在Proct中实现接口中的accept方法 直接调用Visitor接口的visitProct方法即可 public   void  accept(Visitor visitor) {     visitor visitProct( this ); }    下面需要实现Visitor接口 类名是CaculateTotalPriceVisitor 实现了计算总价格的业务逻辑 实现代码如下所示 public   class  CaculateTotalPriceVisitor  implements  Visitor {      private   double  totalPrice;          public   void  visitBrand(Brand brand) {     }      public   void  visitSofareSet(SofareSet sofareSet) {     }      public   void  visitProct(Proct proct) {          //每次在组合的结构中碰到Proct对象节点的时候 就会调用此方法         totalPrice += proct getPrice();     }      public   double  getTotalPrice() {          return  totalPrice;     } }    上面那段代码中 首先在类内定义一个总价格的属性 由于Brand和SofareSet都没有价格 因此在实现中 只需在visitProct方法中累加totalPrice即可 在外面如果需要计算总价格的话这样写(在本文的例子 test business SofareManager中可以找到这段代码) //建立一个新的Visitor对象 CaculateTotalPriceVisitor visitor =  new  CaculateTotalPriceVisitor(); //将该visitor对象传到结构中 data accept(visitor); //调用visitor对象的getTotalPrice()方法就返回了总价格 double  price = visitor getTotalPrice();

  下面是它的时序图 在类SofareManager中的main方法中 调用软件集对象(data)的accept方法 并将生成的visitor对象传给它 accept方法开始递归调用各个子对象的accept方法 如果当前的对象是SofareSet的实例 则调用visitor对象visitSofareSet方法 在visitor对象中对该节点的数据进行一些处理 然后返回 依次类推 遍历到Brand对象和Proct对象也与此类似 当前的逻辑是计算软件产品的总价格 因此当遍历到Proct对象的时候 取出产品的价格并且累加 最后当结构遍历完毕后 调用visitor对象的getTotalPrice方法返回给定软件集对象的(data)的总的价格 如果需要加入一个新的计算逻辑 只实现Visitor接口 并且将该类的实例传给data对象的accept方法就可以实现不同的逻辑方法了

  

  点击小图看大图

  我们可以看到通过访问者模式很好地解决了如何加入新的业务代码而无需重新改动 编译既有代码 但是该模式也不是没有缺点 如果在组合模式中结构加入新的子类的话会导致接口Visitor也跟着改动 导致所有Visitor的子类都需要实现新增的方法 因此这种访问者模式适合于结构不经常变动的情况

  改进访问者模式

  前面我们说到了如何使用Visitor模式及使用该模式后的优缺点 下面举具体的例子说明 假设现在客户提出了一个产品集(ProctSet)的概念 随着公司软件版本的增多 需要将同一个版本的产品(Proct)都放到产品集(ProctSet)中 而一个品牌包含有多个产品集 因为现在组合结构中增加了一个节点 所以在Visitor接口中也必须随之增加一个叫做visitProctSet的方法 并且会导致原有系统中所有已经实现了Visitor接口的类都需要重新实现并编译 用Java的反射机制可以解决这个问题

  使用Java的Method Reflection机制实现访问者模式

  首先我们需要改变一下Visitor接口 接口名叫做ReflectionVisitor 如下所示

public   interface  ReflectionVisitor {      /**      * 定义了一个访问节点的方法      * @param sofareComposite      */      public   void  visitSofareComposite( Object  sofareComposite); }

  在现在的接口的方法里 能接受任意的对象(参数是Object)

  下面实现接口ReflectionVisitor 名叫ReflectionVisitorImpl 代码如下所示

public   class  ReflectionVisitorImpl  implements  ReflectionVisitor {      public   void  visitSofareComposite( Object  sofareComposite) {          //判断是否是null          if  (sofareComposite ==  null ) {              throw   new   NullPointerException ( The visit node should not be null! );         }          //组装class数组 即调用动态方法的时候参数的类型          Class [] classes =  new   Class [] { sofareComposite getClass() };          //组装与class数组相对应的值          Object [] objects =  new   Object [] { sofareComposite };          try  {              //查找visit方法             Method m = getClass() getMethod( visit  classes);              //调用该方法             m invoke( this  objects);         }  catch  ( NoSuchMethodException  e) {              //没有找到相应的方法              System out                      println( You did not implement the visit method for class:                             + sofareComposite getClass());         }  catch  ( Exception  e) {              //发生了别的异常              System out println( Catched excepction in visit method );             e printStackTrace();         }     } }

  这段代码首先判断传入的对象是否是空指针 然后创建class数组和object数组 然后用getMethod方法取得方法名是 visit 方法的参数是 对象sofareComposite对应的类 的方法 最后调用该方法 调用该方法的时候可能会发生NoSuchMethodException异常 发生这个异常就表明它的子类或者当前类中没有与参数中传入相对应的visit方法

  下面再来写新版本Visitor类 扩展刚写好的那个ReflectionVisitorImpl类 名叫CaculateTotalPriceReflectionVisitor 如下所示

public   class  CaculateTotalPriceReflectionVisitor  extends  ReflectionVisitorImpl {      private   double  totalPrice;      public   void  visit(Proct proct) {         totalPrice += proct getPrice();     }      public   void  visit(SofareSet sofareSet) {          System out println( No price for sofare set );     }      public   double  getTotalPrice() {          return  totalPrice;     } }    代码中声明了两个visit方法(因为在类ReflectionVisitorImpl中 查找名为visit 参数与传进去的对象匹配的的方法) 一个是给Proct的 另外一个是给SofareSet的 在这里SofareSet中并没有价格 只需当前的对象是类Proct的实例的时候将价格累加即可 如果在组合模式的结构中增加了新的类 只需要在ReflectionVisitorImpl的扩展类中声明一个visit方法 该方法的参数是新增加的类 对于文中的例子 只需增加下面的一个方法 public   void  visit(ProctSet proctSet) {      //实现的代码 }

  在组合结构的接口SofareComponent中改一下accept方法 参数是修改后的Visitor接口 如下所示

  public void accept(ReflectionVisitor visitor);

  由于在类SofareSet Brand和ProctSet中实现上面accept方法的代码都一样 因此把代码抽象到上层共有的抽象类AbsSofareComposite中 如下所示

public   void  accept(ReflectionVisitor visitor) {     visitor visitSofareComposite( this );     Iterator it = erator();      while  (it hasNext()) {         SofareComponent ponent = (SofareComponent) it next();          //递归调用子对象的accept方法         ponent accept(visitor);     } }    现在如果想在外面要调用的话 代码如下所示(在本文的例子 test business SofareManager中可以找到这段代码) //建立一个新的Visitor对象 CaculateTotalPriceReflectionVisitor reflectionVisitor      =  new  CaculateTotalPriceReflectionVisitor(); //将该visitor对象传到结构中 data accept(reflectionVisitor); //调用visitor对象的getTotalPrice()方法就返回了总价格 double  price = reflectionVisitor getTotalPrice();

  另外由于没有实现Brand类的visit方法 在组合结构遍历到Brand的节点的时候会抛出NoSuchMethodException异常 就是没有关于该节点方法的实现 在当前的程序中会打印出一句话

  You did not implement the visit method for class:class test entity Brand

  如果运行程序时发生了别的异常 请参见相应的Java API文档

  在现在的改进后的访问者模式中 如果在组合的结构中新增或删除节点并不会对已经实现了的Visitor产生任何影响 如果新增了业务方法 只需扩展类ReflectionVisitorImpl就可以了 因此很好地解决了访问者模式的问题

  改进访问者模式实现与既有代码对接

  到现在为止 改进后的访问者模式好像已经很好地解决了所有出现的问题 但是考虑到有下面的这种情况 现在需要写一个JSP的标签库(TagLib) 这个标签库还必须具有Visitor的功能(就是需要有遍历节点的功能) 可以将节点的内容根据需要打印到HTML页面中 由于标签本身需要继承相应的类(如TagSupport) 如果继续使用上面提供的方法将无法实现 因为Java不允许多重继承 不过我们可以将原有ReflectionVisitorImpl的代码再改进一下以解决这种情况 新的Visitor的实现类叫NewReflectionVisitorImpl 代码如下所示

public   class  NewReflectionVisitorImpl  implements  ReflectionVisitor {      // 实现visit方法的类           private   Object  targetObject;          //构造方法 传入实现了visit方法的类      public  NewReflectionVisitorImpl( Object  targetObject) {          if  (targetObject ==  null )              throw   new   NullPointerException (                      The target object should not be null! );          this targetObject = targetObject;     }      public   void  visitSofareComposite( Object  sofareComposite) {          //……与上个例子相同          try  {              // 从目标的对象中查找visit方法             Method m = targetObject getClass() getMethod( visit  classes);              // 调用该方法             m invoke(targetObject  objects);         }  catch  ( NoSuchMethodException  e) {              //……与上个例子相同         }  catch  ( Exception  e) {              //……与上个例子相同         }     } }    该类的实现与上面的实现差不多 多了一个构造函数 在该构造函数的参数中传入实现了visit方法的类 并且维护了指向该类的一个引用 另外最重要的地方是下面的两行代码 // 从目标的对象中查找visit方法 Method m = targetObject getClass() getMethod( visit  classes); // 调用该方法 m invoke(targetObject  objects);

  本来的代码中从本身的类及其子类中查找visit方法 而现在是从维护的目标类中查找visit方法

  现在需要写Tag类 这个类扩展了TagSupport类 如下所示(为说明的方便 随本文的例子提供了一个模拟的TagSupport类)

public   class  MyTag  extends  TagSupport {     SofareComponent sofareComponent =  null ;      private   double  totalPrice =  ;      public   int  doEngTag() {          //创建一个visitor对象 并且将本身传入visitor对象中         ReflectionVisitor visitor =  new  NewReflectionVisitorImpl( this );          //遍历结构         sofareComponent accept(visitor);          //打印出价格         out println(totalPrice);          return   ;     }      //实现了针对Proct的visit方法      public   void  visit(Proct proct) {         totalPrice += proct getPrice();     }      public   void  visit(Brand brand) {         out println(brand getId() + brand getDescription());     }      //别的代码请参见随本文带的源程序     …… }    如果想测试上面写的那段代码 (在本文的例子 test business SofareManager中可以找到这段代码))如下所示 //getMockData()方法返回数据 SofareComponent data = getMockData(); MyTag myTag =  new  MyTag(); myTag setSofareComponent(data); //计算总价格 并打印出来 myTag doEngTag();

  可以看到通过Java的反射机制很好地解决了多重继承的问题 使该访问者模式能够更好地应用于你的应用中 另外可以看到 那些visit方法所在的类已经不是实现了接口ReflectionVisitor 可以说是访问者模式在Java语言的支持下的一种特殊实现

  如果担心引入类反射机制后带来的效率问题 你可以将Method对象通过某种方式缓冲起来 这样不会每次从传入的对象中找visit方法 可以部分地提高效率

  结论

lishixin/Article/program/Java/gj/201311/11152

热心网友 时间:2023-11-24 08:33

  本文从一个给定的实现了组合(Composite)模式的例子开始 说明怎么在这个数据结构上实现业务逻辑代码 依次介绍了非面向对象的方式 在组合结构中加入方法 使用访问者(Visitor)模式以及用改进后的访问者(Visitor)模式来实现相同的业务逻辑代码 并且对于每种实现分别给出了优缺点

  读者定位于具有Java程序开发和设计模式经验的开发人员

  读者通过本文可以学到如何在组合(Composite)模式中实现各种不同的业务方法及其优缺点

  组合(Composite)模式

  组合模式是结构型模式中的一种 GOF的《设计模式》一书中对使用组合模式的意图描述如下 将对象组合成树形结构以表示 部分 整体 的层次结构 Composite使得用户对单个对象和组合对象的使用具有一致性

  组合模式应用广泛 根据GOF中对组合模式的定义 Composite模式一般由Component接口 Leaf类和Composite类组成 现在需要对一个软件产品管理系统的实体建模 某公司开发了一系列软件集(SofareSet) 包含了多种品牌(Brand)的软件产品 就象IBM提供了Lotus WebsPhere等品牌 每个品牌下面又有各种产品(Proct) 如IBM的Lotus下面有Domino Server/Client产品等 建模后的类图如下(代码可以参见随本文带的附件中 包 test entity下所有的源文件)

  

  如图所示

  ( )接口SofareComponent就是对应于组合模式中的Component接口 它定义了所有类共有接口的缺省行为

  ( )AbsSofareComposite类对应于Composite类 并且是抽象类 所有可以包含子节点的类都扩展这个类 这个类的主要功能是用来存储子部件 实现了接口中的方法 部分可以重用的代码写在此类中

  ( )SofareSet类继承于AbsSofareComposite类 对应于软件集 软件集下直接可以包含品牌(Brand) 也可以直接包含不属于任何品牌的产品(Proct)

  ( )Brand类继承于AbsSofareComposite类 对应于品牌 包含了品牌名属性 并且用来存储Proct类的实例

  ( )Proct类就是对应的Leaf类 表示叶子节点 叶子节点没有子节点

  用不同的方法实现业务逻辑

  数据结构建立好之后 需要在这个数据结构上添加方法实现业务逻辑 比如现在的这个例子中 有这样的需求 给定一些用户选择好的产品 需要计算出这些选中后软件的总价格 下面开始介绍如何使用各种不同的方法来实现这个业务逻辑

  非面向对象的编程方式

  这种方式下 编程思路最简单 遍历SofareSet实例中的所有节点 如果遍历到的当前对象是Proct的话就累加 否则继续遍历下一层直到全部遍历完毕 代码片断如下

/**  * 取得某个SofareComponent对象下面所有Proct的价格  * @param brand  * @return  */ public   double  getTotalPrice(SofareComponent sofareComponent) {     SofareComponent temp = sofareComponent;      double  totalPrice =  ;      //如果传入的实例是SofareSet的类型      if  (temp  instanceof  SofareSet) {         Iterator it = ((SofareSet) sofareComponent) getChilds()                &erator();          while  (it hasNext()) { //遍历             temp = (SofareComponent) it next();              //如果子对象是Proct类型的 直接累加              if  (temp  instanceof  Proct) {                 Proct proct = (Proct) temp;                 totalPrice += proct getPrice();             }  else   if  (temp  instanceof  Brand) {               //如果子对象是Brand类型的 则遍历Brand下面所有的产品并累加                 Brand brand = (Brand) temp;                 totalPrice += getBrandPrice(brand);             }         }     }  else   if  (temp  instanceof  Brand) {          //如果传入的实例是SofareSet的类型 则遍历Brand下面所有的产品并累加         totalPrice += getBrandPrice((Brand) temp);     }  else   if  (temp  instanceof  Proct) {          //如果子对象是Proct类型的 直接返回价格          return  ((Proct) temp) getPrice();     }      return  totalPrice; } /**  * 取得某个Brand对象下面所有Proct的价格  * @param brand  * @return  */ private   double  getBrandPrice(Brand brand) {     Iterator brandIt = brand getChilds(erator();      double  totalPrice =  ;      while  (brandIt hasNext()) {         Proct proct = (Proct) brandIt next();         totalPrice += proct getPrice();     }      return  totalPrice; }

  这段代码的好处是实现业务逻辑的时候无需对前面已经定好的数据结构做改动 并且效率比较高 缺点是代码凌乱而且频繁使用了instanceof判断类型和强制类型转换 代码的可读性不强 如果层次多了代码就更加混乱

  面向对象的编程方式(将计算价格的方法加入数据结构中)

  下面我们采用面向对象的方式 可以这么做 在接口SoftWareComponent中加入一个方法 名叫getTotalPrice 方法的声明如下

/**  * 返回该节点中所有子节点对象的价格之和  * @return  */ public   double  getTotalPrice();   由于类Brand和SofareSet都继承了AbsSofareComposite 我们只需在类AbsSofareComposite中实现该方法getTotalPrice方法即可 如下 public   double  getTotalPrice() {     Iterator it = erator();      double  price =  ;      while  (it hasNext()) {         SofareComponent sofareComponent = (SofareComponent) it next();                         //自动递归调用各个对象的getTotalPrice方法并累加         price += sofareComponent getTotalPrice();     }      return  price; }   在Proct类中实现如下 public   double  getTotalPrice(){      return  price; }   在外面需要取得某个对象的总价格的时候只需这样写(在本文的例子 test business SofareManager中可以找到这段代码) // getMockData()方法返回数据 SofareComponent data = getMockData(); //只需直接调用data对象的getTotalPrice 方法就可以返回该对象下所有proct对象的价格 double  price = data  getTotalPrice(); //找到某个对象后直接调用其getTotalPrice方法也可以返回总价格 price = data  findSofareComponentByID( id ) getTotalPrice();

  现在把业务逻辑的实现都放在了数据结构中(组合模式的结构中) 好处很明显 每个类只管理自己相关的业务代码的实现 跟前面举的面向过程方式的实现方式相比 没有了instanceof和强制类型转换 但是不好的地方是如果需要增加新的业务方法的话就很麻烦 必须在接口SoftWareComponent中首先声明该方法 然后在各个子类中实现并且重新编译

  使用访问者模式

  使用访问者模式就能解决上面提到的问题 如果要经常增加或者删除业务功能方法的话 需要频繁地对程序进行重新实现和编译 根据面向对象设计原则之一的SRP(单一职责原则)原则 如果一个类承担了多于一个的职责 那么引起该类变化的原因就会有多个 就会导致脆弱的设计 在发生变化时 原有的设计可能会遭到意想不到的破坏 下面我们引入了一个叫做Visitor的接口 该接口中定义了针对各个子类的访问方法 如下所示

public   interface  Visitor {      public   void  visitBrand(Brand brand);      public   void  visitSofareSet(SofareSet sofareSet);      public   void  visitProct(Proct proct); }   visitBrand方法是访问Brand对象节点的时候用的 剩下的方法依次类推 并在接口SofareComponent中增加一个方法 public   void  accept(Visitor visitor);   在SofareSet中实现接口中的accept方法 首先直接调用Visitor接口中的visitSofareSet方法 传入的参数是本身对象 然后递归调用子对象的accept方法 public   void  accept(Visitor visitor) {     visitor visitSofareSet( this );     Iterator it = erator();      while  (it hasNext()) {         SofareComponent ponent = (SofareComponent)it next();         ponent accept(visitor);     } }    在Brand中实现接口中的accept方法 首先直接调用Visitor接口中的visitBrand方法 传入的参数是本身对象 然后递归调用子对象的accept方法 public   void  accept(Visitor visitor) {     visitor visitBrand( this );     Iterator it = erator();      while  (it hasNext()) {         SofareComponent ponent = (SofareComponent)it next();         ponent accept(visitor);     } }    其实在上面的两个类的实现中可以将遍历子节点并调用其accept方法的代码写到父类AbsSofareComposite中的某个方法中 然后直接调用父类中的这个方法即可 这里为了解释方便分别写在了两个子类中     在Proct中实现接口中的accept方法 直接调用Visitor接口的visitProct方法即可 public   void  accept(Visitor visitor) {     visitor visitProct( this ); }    下面需要实现Visitor接口 类名是CaculateTotalPriceVisitor 实现了计算总价格的业务逻辑 实现代码如下所示 public   class  CaculateTotalPriceVisitor  implements  Visitor {      private   double  totalPrice;          public   void  visitBrand(Brand brand) {     }      public   void  visitSofareSet(SofareSet sofareSet) {     }      public   void  visitProct(Proct proct) {          //每次在组合的结构中碰到Proct对象节点的时候 就会调用此方法         totalPrice += proct getPrice();     }      public   double  getTotalPrice() {          return  totalPrice;     } }    上面那段代码中 首先在类内定义一个总价格的属性 由于Brand和SofareSet都没有价格 因此在实现中 只需在visitProct方法中累加totalPrice即可 在外面如果需要计算总价格的话这样写(在本文的例子 test business SofareManager中可以找到这段代码) //建立一个新的Visitor对象 CaculateTotalPriceVisitor visitor =  new  CaculateTotalPriceVisitor(); //将该visitor对象传到结构中 data accept(visitor); //调用visitor对象的getTotalPrice()方法就返回了总价格 double  price = visitor getTotalPrice();

  下面是它的时序图 在类SofareManager中的main方法中 调用软件集对象(data)的accept方法 并将生成的visitor对象传给它 accept方法开始递归调用各个子对象的accept方法 如果当前的对象是SofareSet的实例 则调用visitor对象visitSofareSet方法 在visitor对象中对该节点的数据进行一些处理 然后返回 依次类推 遍历到Brand对象和Proct对象也与此类似 当前的逻辑是计算软件产品的总价格 因此当遍历到Proct对象的时候 取出产品的价格并且累加 最后当结构遍历完毕后 调用visitor对象的getTotalPrice方法返回给定软件集对象的(data)的总的价格 如果需要加入一个新的计算逻辑 只实现Visitor接口 并且将该类的实例传给data对象的accept方法就可以实现不同的逻辑方法了

  

  点击小图看大图

  我们可以看到通过访问者模式很好地解决了如何加入新的业务代码而无需重新改动 编译既有代码 但是该模式也不是没有缺点 如果在组合模式中结构加入新的子类的话会导致接口Visitor也跟着改动 导致所有Visitor的子类都需要实现新增的方法 因此这种访问者模式适合于结构不经常变动的情况

  改进访问者模式

  前面我们说到了如何使用Visitor模式及使用该模式后的优缺点 下面举具体的例子说明 假设现在客户提出了一个产品集(ProctSet)的概念 随着公司软件版本的增多 需要将同一个版本的产品(Proct)都放到产品集(ProctSet)中 而一个品牌包含有多个产品集 因为现在组合结构中增加了一个节点 所以在Visitor接口中也必须随之增加一个叫做visitProctSet的方法 并且会导致原有系统中所有已经实现了Visitor接口的类都需要重新实现并编译 用Java的反射机制可以解决这个问题

  使用Java的Method Reflection机制实现访问者模式

  首先我们需要改变一下Visitor接口 接口名叫做ReflectionVisitor 如下所示

public   interface  ReflectionVisitor {      /**      * 定义了一个访问节点的方法      * @param sofareComposite      */      public   void  visitSofareComposite( Object  sofareComposite); }

  在现在的接口的方法里 能接受任意的对象(参数是Object)

  下面实现接口ReflectionVisitor 名叫ReflectionVisitorImpl 代码如下所示

public   class  ReflectionVisitorImpl  implements  ReflectionVisitor {      public   void  visitSofareComposite( Object  sofareComposite) {          //判断是否是null          if  (sofareComposite ==  null ) {              throw   new   NullPointerException ( The visit node should not be null! );         }          //组装class数组 即调用动态方法的时候参数的类型          Class [] classes =  new   Class [] { sofareComposite getClass() };          //组装与class数组相对应的值          Object [] objects =  new   Object [] { sofareComposite };          try  {              //查找visit方法             Method m = getClass() getMethod( visit  classes);              //调用该方法             m invoke( this  objects);         }  catch  ( NoSuchMethodException  e) {              //没有找到相应的方法              System out                      println( You did not implement the visit method for class:                             + sofareComposite getClass());         }  catch  ( Exception  e) {              //发生了别的异常              System out println( Catched excepction in visit method );             e printStackTrace();         }     } }

  这段代码首先判断传入的对象是否是空指针 然后创建class数组和object数组 然后用getMethod方法取得方法名是 visit 方法的参数是 对象sofareComposite对应的类 的方法 最后调用该方法 调用该方法的时候可能会发生NoSuchMethodException异常 发生这个异常就表明它的子类或者当前类中没有与参数中传入相对应的visit方法

  下面再来写新版本Visitor类 扩展刚写好的那个ReflectionVisitorImpl类 名叫CaculateTotalPriceReflectionVisitor 如下所示

public   class  CaculateTotalPriceReflectionVisitor  extends  ReflectionVisitorImpl {      private   double  totalPrice;      public   void  visit(Proct proct) {         totalPrice += proct getPrice();     }      public   void  visit(SofareSet sofareSet) {          System out println( No price for sofare set );     }      public   double  getTotalPrice() {          return  totalPrice;     } }    代码中声明了两个visit方法(因为在类ReflectionVisitorImpl中 查找名为visit 参数与传进去的对象匹配的的方法) 一个是给Proct的 另外一个是给SofareSet的 在这里SofareSet中并没有价格 只需当前的对象是类Proct的实例的时候将价格累加即可 如果在组合模式的结构中增加了新的类 只需要在ReflectionVisitorImpl的扩展类中声明一个visit方法 该方法的参数是新增加的类 对于文中的例子 只需增加下面的一个方法 public   void  visit(ProctSet proctSet) {      //实现的代码 }

  在组合结构的接口SofareComponent中改一下accept方法 参数是修改后的Visitor接口 如下所示

  public void accept(ReflectionVisitor visitor);

  由于在类SofareSet Brand和ProctSet中实现上面accept方法的代码都一样 因此把代码抽象到上层共有的抽象类AbsSofareComposite中 如下所示

public   void  accept(ReflectionVisitor visitor) {     visitor visitSofareComposite( this );     Iterator it = erator();      while  (it hasNext()) {         SofareComponent ponent = (SofareComponent) it next();          //递归调用子对象的accept方法         ponent accept(visitor);     } }    现在如果想在外面要调用的话 代码如下所示(在本文的例子 test business SofareManager中可以找到这段代码) //建立一个新的Visitor对象 CaculateTotalPriceReflectionVisitor reflectionVisitor      =  new  CaculateTotalPriceReflectionVisitor(); //将该visitor对象传到结构中 data accept(reflectionVisitor); //调用visitor对象的getTotalPrice()方法就返回了总价格 double  price = reflectionVisitor getTotalPrice();

  另外由于没有实现Brand类的visit方法 在组合结构遍历到Brand的节点的时候会抛出NoSuchMethodException异常 就是没有关于该节点方法的实现 在当前的程序中会打印出一句话

  You did not implement the visit method for class:class test entity Brand

  如果运行程序时发生了别的异常 请参见相应的Java API文档

  在现在的改进后的访问者模式中 如果在组合的结构中新增或删除节点并不会对已经实现了的Visitor产生任何影响 如果新增了业务方法 只需扩展类ReflectionVisitorImpl就可以了 因此很好地解决了访问者模式的问题

  改进访问者模式实现与既有代码对接

  到现在为止 改进后的访问者模式好像已经很好地解决了所有出现的问题 但是考虑到有下面的这种情况 现在需要写一个JSP的标签库(TagLib) 这个标签库还必须具有Visitor的功能(就是需要有遍历节点的功能) 可以将节点的内容根据需要打印到HTML页面中 由于标签本身需要继承相应的类(如TagSupport) 如果继续使用上面提供的方法将无法实现 因为Java不允许多重继承 不过我们可以将原有ReflectionVisitorImpl的代码再改进一下以解决这种情况 新的Visitor的实现类叫NewReflectionVisitorImpl 代码如下所示

public   class  NewReflectionVisitorImpl  implements  ReflectionVisitor {      // 实现visit方法的类           private   Object  targetObject;          //构造方法 传入实现了visit方法的类      public  NewReflectionVisitorImpl( Object  targetObject) {          if  (targetObject ==  null )              throw   new   NullPointerException (                      The target object should not be null! );          this targetObject = targetObject;     }      public   void  visitSofareComposite( Object  sofareComposite) {          //……与上个例子相同          try  {              // 从目标的对象中查找visit方法             Method m = targetObject getClass() getMethod( visit  classes);              // 调用该方法             m invoke(targetObject  objects);         }  catch  ( NoSuchMethodException  e) {              //……与上个例子相同         }  catch  ( Exception  e) {              //……与上个例子相同         }     } }    该类的实现与上面的实现差不多 多了一个构造函数 在该构造函数的参数中传入实现了visit方法的类 并且维护了指向该类的一个引用 另外最重要的地方是下面的两行代码 // 从目标的对象中查找visit方法 Method m = targetObject getClass() getMethod( visit  classes); // 调用该方法 m invoke(targetObject  objects);

  本来的代码中从本身的类及其子类中查找visit方法 而现在是从维护的目标类中查找visit方法

  现在需要写Tag类 这个类扩展了TagSupport类 如下所示(为说明的方便 随本文的例子提供了一个模拟的TagSupport类)

public   class  MyTag  extends  TagSupport {     SofareComponent sofareComponent =  null ;      private   double  totalPrice =  ;      public   int  doEngTag() {          //创建一个visitor对象 并且将本身传入visitor对象中         ReflectionVisitor visitor =  new  NewReflectionVisitorImpl( this );          //遍历结构         sofareComponent accept(visitor);          //打印出价格         out println(totalPrice);          return   ;     }      //实现了针对Proct的visit方法      public   void  visit(Proct proct) {         totalPrice += proct getPrice();     }      public   void  visit(Brand brand) {         out println(brand getId() + brand getDescription());     }      //别的代码请参见随本文带的源程序     …… }    如果想测试上面写的那段代码 (在本文的例子 test business SofareManager中可以找到这段代码))如下所示 //getMockData()方法返回数据 SofareComponent data = getMockData(); MyTag myTag =  new  MyTag(); myTag setSofareComponent(data); //计算总价格 并打印出来 myTag doEngTag();

  可以看到通过Java的反射机制很好地解决了多重继承的问题 使该访问者模式能够更好地应用于你的应用中 另外可以看到 那些visit方法所在的类已经不是实现了接口ReflectionVisitor 可以说是访问者模式在Java语言的支持下的一种特殊实现

  如果担心引入类反射机制后带来的效率问题 你可以将Method对象通过某种方式缓冲起来 这样不会每次从传入的对象中找visit方法 可以部分地提高效率

  结论

lishixin/Article/program/Java/gj/201311/11152
在组合模式中实现访问者(Visitor)模式

本文从一个给定的实现了组合(Composite)模式的例子开始 说明怎么在这个数据结构上实现业务逻辑代码 依次介绍了非面向对象的方式 在组合结构中加入方法 使用访问者(Visitor)模式以及用改进后的访问者(Visitor)模式来实现相同的业务逻辑代码 并且对于每种实现分别给出了优缺点 读者定位于具有Java程序开发和设计模式经验的...

设计模式之访问者模式详解

访问者模式是一种设计模式,它通过分离对象结构的访问行为和元素的实现,实现了对象结构的独立访问,便于扩展新的操作。模式的核心包括抽象访问者(Visitor)、具体访问者(ConcreteVisitor)、抽象元素(AbstractElement)和具体元素(ConcreteElement)以及对象结构(ObjectStructure)等角色。在实践中,抽象访问者...

设计模式都有哪些?

一、创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 二、结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 三、行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模...

设计模式的设计原则

Proxy(代理模式):为其他对象提供一个代理以控制对这个对象的访问。Singleton(单例模式):保证一个类仅有一个实例,并提供一个访问它的全局访问点。 单例模式是最简单的设计模式之一,但是对于Java的开发者来说,它却有很多缺陷。在九月的专栏中,David Geary探讨了单例模式以及在面对多线程(multi-threading)、类装载器...

巧记设计模式设计模式

Strategy模式(策略模式)定义了一系列的算法,让它们可以相互替换,而无需修改使用算法的客户。Template Method模式(模板方法模式)定义了一个算法的骨架,而将一些步骤延迟到子类中实现,实现了代码的复用和扩展。最后,Visitor模式(访问者模式)使得算法的变化独立于使用它的对象,实现了算法的独立于数据...

常用设计模式概览:提高程序可维护性和可扩展性的秘密武器?

单例模式可以帮助我们确保全局只有一个实例,并且可以让我们方便地访问这个实例。 4、建造者模式(Builder) 建造者模式是一种用于创建复杂对象的模式。它将对象的构造过程分解成多个步骤,并且允许在每个步骤中使用不同的构造方法。这样,就可以创建出不同的对象。建造者模式可以帮助我们解决创建复杂对象时的复杂性。 5、...

java中的设计模式如何分类

1.职责链模式 Chain of Responsibility 2.命令模式 Command 3.解释器模式 Interpreter 4.迭代器模式 Iterator 5.中介者模式 Mediator 6.备忘录模式 Memento 7.观察者模式 Observer 8.状态模式 State 9.策略模式 Strategy 10.模板方法模式 Template Method 11.访问者模式 Visitor 其他看参考资料 ...

WIMP编程模式是什么?

十四、Visitor,访问者模式:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这个元素的新操作。十五、Interpreter,解释器模式:给定一个语言,定义他的文法的一个表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。十六、Memento,备忘录模式:在...

python面试一般问什么常用设计模式

访问者模式(Visitor);调停者模式(Mediator);备忘录模式(Memento);迭代器模式(Iterator);解释器模式(Interpreter)。3. 设计模式的六大原则 1、开闭原则(Open Close Principle)开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。

设计模式(五)行为型模式

中介者模式是指,在原本直接通信的对象之间,添加一个通信中间层,使对象间通信变为间接通信,降低对象间的耦合。 此模式和代理模式基本思想上是一致的。二者的区别是:代理模式是通过加一个中间层,来实现两个原本很难交互的功能主体,实现顺畅交互;中介者模式是为了降低对象间通信时的耦合而提出的,为的是提高代码的可...

访问者模式和观察者模式 访问者模式的应用实例 访问者模式详解 访问者模式 应用场景 php访问者模式 访问者模式对开闭原则 访问者模式对开闭原则是否支持 java访问者模式 访问者模式点餐
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
k白金怎么算纯度? 庞大的反义词 庞大反义词 我是一个12岁的小学生,明天就要考400米了,我想知道跑400米的技巧,如前... ...以下哪个故事做到了“悌”?A许武教弟B牛弘不问C礼贤下士D鸠占... ...家长说要给我一个老年机用,我管同学借了一个手机 被发现 怎么解释... 我的月考作文500字 自己在家怎么做零食呢? 坚果零食棒怎样做味道比较香? 零食怎么做好吃 为什么键盘数字键打不出数字? 空气外循环和空气内循环得区别? 男人开始对你不耐烦应该怎么做 男朋友开始态度不耐烦? 公安回流案是什么意思? 深圳市世纪安软信息技术有限公司的软件产品体系 警务类软硬件产品体系概述 15岁的标准身高和体重 男人开始对你态度不耐烦是不爱了吗?说随便你怎么想 当你男人对你说话开始不耐烦咯、证明什么捏? 才知道出去上班真的很难!我到底咋办? 一个人变得越来越不耐烦是为什么? 什么样的表现能看出一个女人,开始变得“不耐烦”? ps不换背景换图片怎么弄 木本水源历千载,报恩追远阅万年,是什么含义 两个人互相吸引的感觉-从这五点看 两个人相互吸引的感觉是怎样 两个人互相吸引是什么感觉 怎么关闭oppo手机自动下载? 6s手机来电话没铃声是怎么回事? 驯良的意思 造句 不锈钢封油和不封油区别 一息尚存的息的意思 一息尚存简单释义 天枰座和白羊座的配对指数详解 息如何组词 发布仅自己可见朋友圈是否审核 朋友圈封面需要审核吗 四月[sì yuè]什么意思?近义词和反义词是什么?英文翻译是什么? 微信使用时网络正常,退出微信使用QQ浏览器却显示网络异常,打不开页面。是什么原因? 宜昌市汽车修理4s店钣金喷漆招聘 我需要一份工作汽车钣金工 请问哪里能招到好的钣金工和底盘工 开县哪里招汽车维修钣金工,我想找份工作 我是高一学生,求“如何提高数学课堂效率”课题研究的目的及意义?!急!!拜托了!! 什么锤与什么槽成语 苹果6来电第一次自动挂断怎么回事? 不管什么电话,打第一次来电都会自动挂断,二手机上会显示未 bot是什么意思,有什么典故吗? 梦到赶苍蝇是什么意思 老人梦见打苍蝇是什么意思 哺育拼音怎么读 qq名片竖屏背景图片怎么设置 面包烤盘架价格是多少