from:http://blog.abreaking.com/c/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F_%E8%AE%BF%E9%97%AE%E8%80%85Visitor%E6%A8%A1%E5%BC%8F
最近在研究一个框架JSqlParser时,发现该框架使用了一种设计模式——访问者(Visitor)模式。遂在网上找了一下该设计模式相关知识,学习整理。
参考:http://www.cnblogs.com/idior/archive/2005/08/18/217500.html
访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。
第一次这句话时,着实费解。关于对访问者模式的理解,可以先见下面这个例子,而后再看最后的综述。
比如:你下午请客喝东西,那么就的考虑好你请的这些人中总会有口味不同,有些人喜欢喝茶,有些人喜欢喝果汁,有人喜欢喝咖啡。你当然只有把这些东西都准备好,尽量准备得全一些。那么来的客人喜欢喝啥就自己拿就是了。
那么我们定义一个基本的访问者操作的接口,反正你也不晓得要来的客人喜欢喝什么,那么就抽象一个Visitor什么都喝。
//基本访问着的操作
public interface Visitor {
void visit(TeaWater teaWater);
void visit(JuiceWater juiceWater);
void visit(CaffeeWater caffeeWater);
}
这时,你的需要准备好要喝的东西来招待你的客人,定义一个方法的接口,不管是什么水,反正用来招待客人。
public interface Water {
void accept(Visitor visitor);
}
此外,你得把具体得水准备好了,准备好三样:茶、咖啡、果汁。不同类型的水也许会有各自的一些属性操作等等。
public class TeaWater implements Water {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String getHotTea(){
return "热茶";
}
public String getColdTea(){
return "凉茶";
}
}
public class CaffeeWater implements Water {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void addSugar(int number){
System.out.print("添加了"+number+"勺糖");
}
}
public class JuiceWater implements Water {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String getJuice(int size){
return size==0?"小杯":"大杯";
}
}
我们再假设几个访问者实体类,假设zhangsan lisi 就是你要邀请的两个人,他们的喜好定义如下:
public class ZhangsanVisitor implements Visitor{
@Override
public void visit(TeaWater teaWater) {
System.out.print("张三只喝茶,");
String hotTea = teaWater.getHotTea();
System.out.println("拿了"+hotTea);
}
@Override
public void visit(JuiceWater juiceWater) {
//不喜欢果汁,什么操作也不做
}
@Override
public void visit(CaffeeWater caffeeWater) {
//不喜欢咖啡,
}
}
public class LisiVisitor implements Visitor {
@Override
public void visit(TeaWater teaWater) {
System.out.print("能喝点茶");
String coldTea = teaWater.getColdTea();
System.out.println(",拿了"+coldTea);
}
@Override
public void visit(JuiceWater juiceWater) {
System.out.print("也能喝果汁");
String juice = juiceWater.getJuice(1);
System.out.println(",还是要"+juice);
}
@Override
public void visit(CaffeeWater caffeeWater) {
System.out.print("咖啡也要喝,");
caffeeWater.addSugar(2);
}
}
好了,接下来就是准备工作啦。你的把桌子准备好,用于放要喝的东西,招待客人。
public class Table {
//准备好所有要喝的东西
List waters = new ArrayList<>();
public void addWater(Water water){
waters.add(water);
}
//招待客人
public void accept(Visitor visitor){
for (Water water : waters){
water.accept(visitor);
}
}
}
万事具备,客人来了,我们写个测试方法。准备好茶、咖啡、果汁来招待zhangsan、lisi。
@Test
public void test01(){
Table table = new Table();
table.addWater(new TeaWater());//摆上茶
table.addWater(new JuiceWater()); //摆上果汁
table.addWater(new CaffeeWater()); //摆上咖啡
Visitor zhangsan = new ZhangsanVisitor();
Visitor lisi = new LisiVisitor();
System.out.println("张三");
table.accept(zhangsan);
System.out.println("李四");
table.accept(lisi);
}
运行结果如下:
以上就是一个访问者模式的基本实例。
关于访问者模式的UML图,可参见如下:
根据之前例子,可看到,对于访问者模式而言,两个最重要的角色:
针对节点角色于访问者角色的方法可看到,节点角色接收一个访问者对象,并把自己作为变量再传给访问者对象,访问者对象再调用节点对象里的逻辑方法,这种操作过程被称为“双重分派”。
此外,对于Table这个对象,就是访问者模式的结构对象角色(ObjectStructure),它就是用于遍历节点中的所有元素。
可以看出,访问者模式适合于操作相对稳定的结构,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。也就是说,对于一个Visitor,它需要有什么操作,必须要非常清楚。如之前的例子,我招待客人,那么我很清楚,我只有茶、咖啡、果汁这个材料,我也只能拿这三个来招待客人。所以在Visitor方法中,就只有那三个方法。这是访问者模式最重要的一点,也是最有缺陷的一点。 如上个例子:有个人还想喝酒,我还得新增加一个节点类,及Water实现类,那么我还得在Visitor中加入一个方法,改动所有的Visitor的实现类,很明显,这样的效率是非常低下的。
但是对于增加新的访问者那就非常的简单,如上的例子,无非就是多来一个客人,也就是新增加一个新Visitor实现类。
所以,访问者适用于具体操作能够非常稳定的情景下,能够很容易的拓展访问者(Visitor)。