訪問者模式(Visitor Pattern):在不改變某對象結構中元素的前提下,定義作用於這些元素的新操作。
訪問者模式適用於數據結構相對穩定的系統,可以把數據結構和作用於結構上的操作解耦,使操作集合可以相對自由地變化。一種操作就相當於一個訪問者。訪問者模式將有關操作行爲集中到一個訪問者對象中。
訪問者模式的缺點在於會使數據結構的變化變得困難。
比如男人和女人對於不同的事件有不同的反應,如果單純抽象出Person基類,Man和Woman根據傳遞進來的不同事件作出不同反應,這樣新增事件的話都要修改Man和Woman,違反了封閉開放原則。
可以把變化的地方,也就是不同的事件,抽象一個基類,然後基於這個基類進行擴展,可以達到比較好的解耦效果。
實例實現
首先定義數據基類,基本方法就是對事件進行反應:
public interface Person {
void react(Event visitor);
}
兩個數據類,訪問者模式一般應用於數據元素比較穩定的情況下:
class Man implements Person {
@Override
public void react(Event visitor) {
visitor.getManReaction(this);
}
}
class Woman implements Person {
@Override
public void react(Event visitor) {
visitor.getWomanReaction(this);
}
}
此處用到一個技術叫雙分派,就是在數據對象的一個方法中,接收事件對象作爲參數,這是一次分派;然後接收到的事件對象調用自己的方法,傳入調用它的數據對象作爲方法參數,這就是第二次分派。雙分派技術意味着方法的執行不僅取決於方法參數,還取決於執行方法的對象,由方法參數和調用者本身共同決定執行結果。
然後定義事件類,也就是模式裏的訪問者,事件類就是定義數據類各自的反應方法,如果數據元素穩定不變的話,事件類的方法也就不用去修改:
public interface Event {
void getManReaction(Man man);
void getWomanReaction(Woman woman);
}
事件類的具體實現:
class Success implements Event {
@Override
public void getManReaction(Man man) {
System.out.println(man.getClass().getSimpleName() + " "
+ this.getClass().getSimpleName() + "時,背後一般有個偉大的女人");
}
@Override
public void getWomanReaction(Woman woman) {
System.out.println(woman.getClass().getSimpleName() + " "
+ this.getClass().getSimpleName() + "時,背後一般有個不成功的男人");
}
}
class Failure implements Event {
@Override
public void getManReaction(Man man) {
System.out.println(man.getClass().getSimpleName() + " "
+ this.getClass().getSimpleName() + "時,獨自借酒澆愁");
}
@Override
public void getWomanReaction(Woman woman) {
System.out.println(woman.getClass().getSimpleName() + " "
+ this.getClass().getSimpleName() + "時,需要人來安慰");
}
}
class Marriage implements Event {
@Override
public void getManReaction(Man man) {
System.out.println(man.getClass().getSimpleName() + " "
+ this.getClass().getSimpleName() + "時,我會照顧你一輩子");
}
@Override
public void getWomanReaction(Woman woman) {
System.out.println(woman.getClass().getSimpleName() + " "
+ this.getClass().getSimpleName() + "時,我願意和你到天荒地老");
}
}
然後我們還可以定義一個數據結構,用來組織數據元素對於不同訪問者的反應結果:
public class DataStructure {
private List<Person> elements = new ArrayList<>();
public void addElement(Person element) {
elements.add(element);
}
public void removeElement(Person element) {
elements.remove(element);
}
public void showReaction(Event visitor) {
for (Person person : elements) {
person.react(visitor);
}
}
}
測試類:
public class Main {
public static void main(String[] args) {
final DataStructure dataStructure = new DataStructure();
final Man man = new Man();
final Woman woman = new Woman();
dataStructure.addElement(man);
dataStructure.addElement(woman);
dataStructure.showReaction(new Success());
System.out.println();
dataStructure.showReaction(new Failure());
System.out.println();
dataStructure.showReaction(new Marriage());
}
}
輸出:
Man Success時,背後一般有個偉大的女人
Woman Success時,背後一般有個不成功的男人
Man Failure時,獨自借酒澆愁
Woman Failure時,需要人來安慰
Man Marriage時,我會照顧你一輩子
Woman Marriage時,我願意和你到天荒地老
訪問者模式使得數據元素不變的情況下,新增事件只要創建新的實現即可,不用修改原有代碼,很好地符合了封閉開放原則。