访问者模式提供了一种方法,将算法和数据结构分离。假设我们需要对一个数据结构进行不同的操作,就可以考虑使用访问者模式。访问者模式的要点在于,需要一个访问者接口,提供了一些重载方法来访问具体对象。对于每个具体对象,又提供了一个accept方法来回调访问者。
首先来看看访问者。
public interface Visitor {
void visit(House house);
void visit(Kitchen kitchen);
void visit(LivingRoom livingRoom);
void visit(BedRoom bedRoom);
}
class HouseVisitor implements Visitor {
public void visit(House house) {
System.out.println("访问了房子");
}
public void visit(BedRoom bedRoom) {
System.out.println("访问了卧室");
}
public void visit(LivingRoom livingRoom) {
System.out.println("访问了客厅");
}
public void visit(Kitchen kitchen) {
System.out.println("访问了厨房");
}
}
然后是要访问的对象,这里是一间屋子。
public class House {
private LivingRoom livingRoom;
private Kitchen kitchen;
private BedRoom bedRoom;
public House() {
livingRoom = new LivingRoom();
kitchen = new Kitchen();
bedRoom = new BedRoom();
}
public void accept(Visitor visitor) {
visitor.visit(this);
livingRoom.accept(visitor);
bedRoom.accept(visitor);
kitchen.accept(visitor);
}
}
class LivingRoom {
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Kitchen {
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class BedRoom {
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
然后客户端就可以简单的访问屋子了。
public void run() {
Visitor visitor = new HouseVisitor();
House house = new House();
visitor.visit(house);
}
这就是访问者模式了。可能会同学会有疑问,为什么我要这么写?如果让屋子对象层次全部都实现Visitor接口,然后客户端直接调用这些visit方法不是也可以吗?一开始我也有这个疑问,后来看了知乎轮子哥的一篇文章ParserGen生成预定义好的各种visitor,感觉茅塞顿开。
其实Visitor模式讲的就是在不需要扩充新的子类的时候,如何添加新的虚函数而不需要修改原有代码。当然虚函数也有它的好处,就是添加新的子类的时候不需要修改原有代码。所以看你的业务逻辑,到底是添加新子类多,还是添加新虚函数多,从而选择要不要把程序写成基于Visitor模式的样子。
对于编译器来说,整个处理流程那么复杂,所以等于需要经常添加虚函数,因此就都把本来是虚函数的东西改成了各种Visitor。这个时候,如果你修改了语法,那么每一个Visitor都会曝出语法错误,所以这等于变相通知你所有需要修改的东西在哪里——如果你能坚持不因为偷懒而使用dynamic_cast的话。
所谓设计模式,都需要在特定的环境中才有用。所以现在我们已经了解了什么情况下应该使用访问者模式。假如接口中的方法固定,但是需要添加新的实现类,那么就使用普通的继承方式。如果接口方法经常变动,就可以把接口改写为访问者。