Visitor Pattern
摘要
設計模式是對設計原則的具體實踐,在編碼過程中我們要牢記設計原則,根據當前需求靈活選用我們要使用的設計模式。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
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 模式是一個反模式,不應該使用。我們辯證地來看待,畢竟這是在語言限制的條件做出的不得已選擇。