摘要
设计模式是对设计原则的具体实践,在编码过程中我们要牢记设计原则,根据当前需求灵活选用我们要使用的设计模式。Visitor Pattern 是一个不常用的模式,在我看来,visitor pattern 也算是面向对象里的一种奇技淫巧了。
what
什么是visitor模式?从Wikipedia 上的定义为:In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates.
. 是将算法与类结构分开的这么一种模式,而且是面向对象独有的。
When
我们在什么条件下才可以使用这个模式呢?有以下几个方面需要满足:
- 有一组提前定义好的类(已知)
- 对象上的操作是未知的
- 对象需要根据不同的类做出不同的操作,因此需要使用(instanceof)判断
操作就是定义中的algorithm,提前定义好的类就是object structure。也就是说由于操作未知,所以将原本属于类中的操作拿出来,放到 visitor 中。
Why
其实按上面的定义是不是感觉 visitor pattern 违反了将类本身的职责放在类中这个简单原则呢?在我看来是的,那为何出现了这种反原则的模式并且堂而皇之的成为了24种模式之一呢?其实这是因为语言自身的问题导致的。visitor pattern 的底层本质是 double dispatching
, 而像Java 这种语言只支持single dispatching
,而要实现 double dispatching
怎么办呢,就只能使用 visitor pattern这种笨拙的模式了。
How
现在我们有两种动物,定义如下:
public abstract class Animal{}
public class Dog extends Animal{}
public class Cat extends Animal{}
我们需要增加一个叫声的方法,由于猫狗的叫声不一样,我们需要将3个类都修改一下。
public abstract class Animal {
public abstract void makeSound();
}
public class Dog extends Animal{
public void makeSound() {
System.out.println("wang wang");
}
}
public class Cat extends Animal{
public void makeSound(){
System.out.println("miao miao");
}
}
过了两天我们发现还需要增加一个eat
的方法,三个类又需要同步修改,违反了OCP原则,这时我们就可以使用Visitor Pattern,将变化提取出来, Animal的三个类保持不动。
public abstract class Animal{}
public class Dog extends Animal{}
public class Cat extends Animal{}
public abstract class AnimalVisitor {
public abstract void visit(Animal animal);
public abstract void visit(Dog dog);
public abstract void visit(Cat cat);
}
public class MakeSoundVisitor extends AnimalVisitor{
public void visit(Animal animal){
System.out.println("animal sound")
}
public void visit(Dog dog){
System.out.println("wang wang");
}
public void visit(Cat cat) {
System.out.println("miao miao");
}
}
public class EatVisitor extends AnimalVisitor {
public void visit(Animal animal){
System.out.println("animal eat");
}
public void visit(Dog dog){
System.out.println("eat dog food");
}
public void visit(Cat cat){
System.out.println("eat cat food");
}
}
很美好,我们每当有什么操作需要添加的时候就新增一个继承了AnimalVisitor
的类,由它来定义具体的行为。我们可以这样使用
Dog dog = new Dog();
Cat cat = new Cat();
EatVisitor eat = new EatVisitor();
eat.visit(dog);
eat.visit(cat);
但这存在一个问题,我们只能使用具体的Dog
Cat
类,而不能使用父类Animal
:
Animal dog = new Dog();
eat.visit(dog);//animal eat
明明dog 是 Dog
类型,调用却到了Animal
上,就是因为Java不具备按运行时类型来做不同的函数调用,也就是上面所说的不支持double dispatching
,才导致了这样的结果。
如何解决这个问题呢?Java是支持方法的动态调用的(single dispatching
),我们可以根据这个来间接实现double dispatching
,也就是由Animal 类族做一次转发。
public abstract class Animal{
public void visit(AnimalVisitor visitor){
visitor.visit(this);
}
}
public class Dog extends Animal{
public void visit(AnimalVisitor visitor){
visitor.visit(this);
}
}
public class Cat extends Animal{
public void visit(AnimalVisitor visitor){
visitor.visit(this);
}
}
很多visitor 模式的例子对不同的类使用了不同的方法名,这里我们在每个子类中的代码与父类中的代码一样,但是this的含义是不一样的。
使用时我们就需要由Visitor 调用 Animal变成Animal 调用Visitor了。
EatVisitor eat = new EatVisitor();
Animal dog = new Dog();
dog.visit(eat);
compare
Visitor Patter 将子类中的实现全部拿到了Visitor中来做,如果熟悉函数式编程的人就会觉得这个模式很面熟。其实这就是函数式编程中强大的模式匹配的一部分,根据不同的子类选择的不同的函数而已。
summary
Visitor 模式使用的很稀少,很大一个原因是它的条件太苛刻,它要求被visit的类族是稳定的,但是做过需求的都知道类是不断变化的。网上也有很多人说visitor 模式是一个反模式,不应该使用。我们辩证地来看待,毕竟这是在语言限制的条件做出的不得已选择。