本节使用访问者模式的基本结构,讨论它的作用和意义,或者说意图。
1.问题的提出
现有这样一个方案:Person有子类Boy、Girl;Person定义了方法say()、eat(int i)、walk()。
package method.visitor;
public abstract class Person{
public abstract void accept(Visitor v);
/*
public abstract String say();
public abstract int eat(int i);//长肉
public abstract void walk();
*/
}
Person的这些代码,由其子类实现。Person非常容易扩展出新的子类。但是,
- (据说eat与身高、心情、减肥等相关,有非常复杂的算法...)据说say()与walk()的代码放在一起,会显得不专业
- 由于Person的这些方法的(实现)代码将分散到Boy、Gril中“导致整个系统难以理解”,
- 更重要的是,Person增添新的方法困难——违反OCP、整个类层次需要修改。
于是,有了访问者模式。从结构出发,访问者模式2中好人打贱人说明了一个事实,只要涉及双分派,就可以使用访问者模式。
2.专项检查
GoF的访问者模式的入手点:将 Person的层次横切为Visitor层次。- Person定义的方法say()、eat(int i)、walk()将被封装为访问子类,添加一个accept()。
- Visitor及其子类将对Person的子类进行访问。访问者是做专项检查的。
Person有n个子类m个方法,转换为Visitor则有m个子类n个方法,虽然n*m=m*n。
访问者模式的最好使用场合:m越大n越小越好。换言之:访问者类型很多而对象结构(如Person)中元素(或子类)类型很少,这才适合访问者模式。
package method.visitor;
/**
* @author yqj2065
* @version 2014.9
*/
public abstract class Visitor{
public abstract void visit(Person p);
abstract void visitBoy();
abstract void visitGirl();
}
package method.visitor;
import static tool.Print.*;
public class SayVisitor extends Visitor{
public void visit(Person p){
p.accept(this);
}
@Override void visitBoy(){
//Boy.say
pln("Boy.say");
}
@Override void visitGirl(){
pln("Girl.say");
}
}//EatVisitor、WalkVisitor略
所以,(Visitor或)EatVisitor表示“
一个作用于某对象结构中的各元素的操作”,EatVisitor表示Person的eat(int i);
所以,Visitor“使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作”。例如,希望为Person增添song()操作,添加一个SongVisitor即可。
现在,可以容易地为Person(通过扩展Visitor)增添新操作;但是Person本身的扩展,由于Visitor的牵绊,很困难。
3.对象结构
GoF的访问者模式中,包含了一个用于存储元素对象集合的对象结构。在讨论访问者模式时,个人从来不关注它。它 不是必需的,但却是常见的、能够理解的。总体而言,我们在学习访问者模式时,可以将“对象结构”省略掉。(如果你觉得有这个对象结构,使得你更容易理解访问者模式,你可以带上它)这个对象结构,可以是任何数据结构,这里演示的Group有一个最常见的ArrayList<Person>保存各种具体元素(Person的各种子类)。可以使用组合模式构造更复杂的数据结构来组织元素,例如将Group设计为Person的子类,等等。但是所有这些和访问者模式并没有实际的关系。
例程 3-31对象结构
package method.visitor;
import java.util.ArrayList;
public class Group{
private ArrayList<Person> list=new ArrayList<>();
public void accept(Visitor visitor){
for(Person x :list){
x.accept(visitor);
}
}
public void add(Person product){ list.add(product); }
public void remove(Person product) { list.remove(product); }
}
假设某个观察者Visitor v1要观察一个小团队Group中的各成员如何说话,Test的代码可以如下:
public static void testGroup(){
Visitor v1 =(Visitor)God.create("3-5-Visitor1");//SayVisitor
// Visitor v2 =(Visitor)God.create("3-5-Visitor2");// new EatVisitor();
// Visitor v3 =(Visitor)God.create("3-5-Visitor3");//new WalkVisitor();
Person a =(Person)God.create("3-5-Person1");// new Boy();
Person b =(Person)God.create("3-5-Person1");// new Boy();
Person c =(Person)God.create("3-5-Person2");// new Girl();
Group g = new Group();
g.add(a);g.add(b);g.add(c);
g.accept(v1);//g.accept(v2);g.accept(v3);
}
按照配置文件,其输出的一例:
Boy.say
Boy.say
Girl.say