代码改变世界

让我再罗嗦一下Visitor模式

2005-08-19 14:13 FantasySoft 阅读(...) 评论(...) 编辑 收藏

        昨天看了idior兄的Visitor模式全解之后,思考了很久,同时自己也摸索着写了些代码,对Vistor模式有了进一步的理解。
        1、使用Vistor模式最大的好处就是增加对数据对象的操作却不需要改变数据对象本身,不需要对数据对象进行重新编译。在这里的数据对象以及所增加的操作都应该满足一定的条件的,如果你所操
作的数据对象仅仅是一个或者所增加的操作仅仅作用一个数据对象集合中的极少数元素的话,那么使用Vistor难免就大材小用了。Visitor最适合使用的情况是:在一个拥有多个对象的多个实例集合中增加新操作;对象集合中的对象之间的联系是密切的,同时增加的新操作应该覆盖了对象集合中全部或者绝大部分元素。以下是我之前做的项目中一个例子:

public abstract class Ticket {
    
protected
 Date issueDate;
    
protected
 Double penalty;
    
protected
 String ticketNo; 
    
// getter and setter method

    public abstract void accept(Vistor v);        

}


public class FppTicket extends Ticket{
    
private
 String block;
    
private
 String building;
    
private
 String street;
    
// getter and setter methods

    public abstract void accpet(Vistor v) {
        v.visit(
this
);
    }

}


public class FpmTicket extends Ticket{
   
private
 String offenceType;
   
// getter and setter methods

   public abstract void accpet(Vistor v) {
       v.visit(
this
);
   }

}
 

FppTicket和FpmTicket是指违规停车和违规行车的罚单。对于两种不同的ticket,我们会做很多方面的统计,如统计某一天有多少罚单,罚款总数是多少等,在这种情况下,使用Vistor模式是相当适合的。于是就会出现PenaltyVistor,IssueDateVistor等等的Vistor:

public abstract class Vistor {
    
public abstract void
 visit(FpmTicket ticket);
    
public abstract void
 visit(FppTicket ticket);
}


public class PenaltyVistor extends Vistor{
    
public void visit(FpmTicket ticket) {}

    
public void visit(FppTicket ticket) {}
}

        2、对于数据对象本身,提供accept这个方法的根本目的在于将对象实例传递给其他对象(Vistor),因此你会发现所有accept方法的样子是一致的:

public void accept(Vistor v) {
    v.visit(
this
);
}

从语义上讲确实是数据结构对象accept了Vistor的visit,但是这样的accept是抽象的,因为对于数据对象本身不知道Vistor是远方朋友还是不速之客。反观Vistor的visit方法,则会存在以不同类型的数据对象为参数的形式,因而在visit方法调用过程中获得数据对象的实例,接下来就可以对不同的数据对象采取不同的操作了。这就是idior震宇兄都反复强调的Visitor模式最重要的一点double-dispatch(双重分派)。
        3、作为父类的Visitor应该为虚类或者接口,同时在Visitor类中定义了访问数据对象集合中所有元素的虚方法,强迫子类实现。这一点
是比较正统的Visitor pattern的实现方式,也是大家诟病最多的地方。正是以这样的方式去组织各个Vistor之间的关系,就造成了往数据对象集合中增加新对象的时候,需要进行大量的改动。idior和震宇兄花了很多精力去减少由增加新对象而带来的改动,最后也有了相当不错的解决办法。但是,我觉得这样做反而没有体现出Vistor模式的特点了。大家可以再回头看一下我总结的第一点,增加的操作应该是作用于数据对象集合的全部或者绝大部分,因此增加了一个新的数据对象,对于Vistor以及由其派生的各个子Visitor都有必要增加访问这个新的数据对象的visit方法。我们可以这样认为,一个Visitor必须对它所visit的数据对象集合中每一个对象有所了解,而不应该一无所知
        4、Visitor的关注点应该是数据对象的共性,而非个性。因此,我们不应该去定义FppTicketVistor或者FpmTicketVistor这样的V
isitor,而PenaltyVistor才符合Vistor模式的语义。然后我们在PenaltyVistor中增加针对不同类型的数据对象的visit方法,这样在Visitor层面上保持了数据对象的一致性(FppTicket和FpmTicket都继承了Ticket),在Visitor内部则体现出数据对象的差异性。
        好了,相应的思考就总结到这里,还请各位多多批评指教了。最后我还想多说一句对模式的肤浅认识:模式的应用就是为了让程序员能够更好的进行协作,为了
让代码的组织更加有序,只有这样才能降低软件开发以及维护的成本,从而扩大软件的规模。尽量减少业务逻辑的变更所带来了的代码改动是应用设计模式的目的,同时更好的应对代码改动同样也是设计模式的目的啊。