【IT168技术文档】
使用访问者模式
使用访问者模式就能解决上面提到的问题:如果要经常增加或者删除业务功能方法的话,需要频繁地对程序进行重新实现和编译。根据面向对象设计原则之一的SRP(单一职责原则)原则,如果一个类承担了多于一个的职责,那么引起该类变化的原因就会有多个,就会导致脆弱的设计,在发生变化时,原有的设计可能会遭到意想不到的破坏。下面我们引入了一个叫做Visitor的接口,该接口中定义了针对各个子类的访问方法,如下所示:
visitBrand方法是访问Brand对象节点的时候用的,剩下的方法依次类推。并在接口SoftwareComponent中增加一个方法:public interface Visitor ...{ public void visitBrand(Brand brand); public void visitSoftwareSet(SoftwareSet softwareSet); public void visitProduct(Product product); }
在SoftwareSet中实现接口中的accept方法,首先直接调用Visitor接口中的visitSoftwareSet方法,传入的参数是本身对象,然后递归调用子对象的accept方法:public void accept(Visitor visitor);
在Brand中实现接口中的accept方法,首先直接调用Visitor接口中的visitBrand方法,传入的参数是本身对象,然后递归调用子对象的accept方法:public void accept(Visitor visitor) ...{ visitor.visitSoftwareSet(this); Iterator it = childs.iterator(); while (it.hasNext()) ...{ SoftwareComponent component = (SoftwareComponent)it.next(); component.accept(visitor); } }
其实在上面的两个类的实现中可以将遍历子节点并调用其accept方法的代码写到父类AbsSoftwareComposite中的某个方法中,然后直接调用父类中的这个方法即可。这里为了解释方便分别写在了两个子类中。public void accept(Visitor visitor) ...{ visitor.visitBrand(this); Iterator it = childs.iterator(); while (it.hasNext()) ...{ SoftwareComponent component = (SoftwareComponent)it.next(); component.accept(visitor); } }
在Product中实现接口中的accept方法,直接调用Visitor接口的visitProduct方法即可:
下面需要实现Visitor接口,类名是CaculateTotalPriceVisitor,实现了计算总价格的业务逻辑,实现代码如下所示:public void accept(Visitor visitor) ...{ visitor.visitProduct(this); }
上面那段代码中,首先在类内定义一个总价格的属性,由于Brand和SoftwareSet都没有价格,因此在实现中,只需在visitProduct方法中累加totalPrice即可。在外面如果需要计算总价格的话这样写(在本文的例子com.test.business.SoftwareManager中可以找到这段代码):public class CaculateTotalPriceVisitor implements Visitor ...{ private double totalPrice; public void visitBrand(Brand brand) ...{ } public void visitSoftwareSet(SoftwareSet softwareSet) ...{ } public void visitProduct(Product product) ...{ //每次在组合的结构中碰到Product对象节点的时候,就会调用此方法 totalPrice += product.getPrice(); } public double getTotalPrice() ...{ return totalPrice; } }
下面是它的时序图:在类SoftwareManager中的main方法中,调用软件集对象(data)的accept方法,并将生成的visitor对象传给它。accept方法开始递归调用各个子对象的accept方法。如果当前的对象是SoftwareSet的实例,则调用visitor对象visitSoftwareSet方法,在visitor对象中对该节点的数据进行一些处理,然后返回;依次类推,遍历到Brand对象和Product对象也与此类似。当前的逻辑是计算软件产品的总价格,因此当遍历到Product对象的时候,取出产品的价格并且累加,最后当结构遍历完毕后,调用visitor对象的getTotalPrice方法返回给定软件集对象的(data)的总的价格。如果需要加入一个新的计算逻辑,只实现Visitor接口,并且将该类的实例传给data对象的accept方法就可以实现不同的逻辑方法了。//建立一个新的Visitor对象 CaculateTotalPriceVisitor visitor = new CaculateTotalPriceVisitor(); //将该visitor对象传到结构中 data.accept(visitor); //调用visitor对象的getTotalPrice()方法就返回了总价格 double price = visitor.getTotalPrice();
我们可以看到通过访问者模式很好地解决了如何加入新的业务代码而无需重新改动、编译既有代码。但是该模式也不是没有缺点:如果在组合模式中结构加入新的子类的话会导致接口Visitor也跟着改动,导致所有Visitor的子类都需要实现新增的方法。因此这种访问者模式适合于结构不经常变动的情况。