訪問者模式
假設(shè)有男人和女人兩種元素,要分別打印出他們?cè)诓煌瑺顟B(tài)時(shí)的不同表現(xiàn)。
用OO的思想把表現(xiàn)(行為)提取出來作為一個(gè)抽象方法,代碼如下:
用if-else對(duì)狀態(tài)進(jìn)行判斷
Person接口
public interface Person {
public void action(String state);
}
Man實(shí)現(xiàn)類
Java代碼 收藏代碼
public class Man implements Person{
public void action(String state) {
if(state == "success"){
System.out.println("當(dāng)男人成功時(shí),背后多半有一個(gè)偉大的女人");
}
else if(state == "love"){
System.out.println("當(dāng)男人戀愛時(shí),凡事不懂也裝懂");
}
}
}
Woman實(shí)現(xiàn)類
Java代碼 收藏代碼
public class Woman implements Person{
public void action(String state) {
if(state == "success"){
System.out.println("當(dāng)女人成功時(shí),背后大多有一個(gè)不成功的男人");
}
else if(state == "love"){
System.out.println("當(dāng)女人戀愛時(shí),遇事懂也裝不懂");
}
}
}
客戶端測(cè)試代碼
Java代碼 收藏代碼
public class Client {
public static void main(String[] args) {
Person man = new Man();
Person woman = new Woman();
man.action("success");
woman.action("success");
man.action("love");
woman.action("love");
}
}
結(jié)果顯示:
當(dāng)男人成功時(shí),背后多半有一個(gè)偉大的女人
當(dāng)女人成功時(shí),背后大多有一個(gè)不成功的男人
當(dāng)男人戀愛時(shí),凡事不懂也裝懂
當(dāng)女人戀愛時(shí),遇事懂也裝不懂
當(dāng)需求發(fā)生變化時(shí),要增加一種失敗狀態(tài)時(shí),增加男人女人的不同表現(xiàn),這時(shí)就要修改Man類與Woman類的if
else,違反了ocp原則(對(duì)增加開放-對(duì)修改封閉)。而且隨著需求的增加,Man類與Woman類的if,else越來越臃腫,需要取消時(shí),又要去修改if,else,既不方便,又容易出錯(cuò)。
這時(shí)候訪問者模式可以派上用場(chǎng)了。
請(qǐng)看下面經(jīng)修改后的類圖:
使用訪問者模式
把狀態(tài)抽象出來成為一個(gè)接口(訪問者),不同的狀態(tài)就作為狀態(tài)的不同實(shí)現(xiàn)類(不同的訪問者)。
狀態(tài)的接口(訪問者接口)
public interface Visitor {
public void visit(Man man);
public void visit(Woman woman);
}
具體訪問者實(shí)現(xiàn)類(分別代表不同的狀態(tài))
Java代碼 收藏代碼
//成功時(shí)Man與Woman的不同表現(xiàn)
public class Success implements Visitor{
public void visit(Man man) {
System.out.println("當(dāng)男人成功時(shí),背后多半有一個(gè)偉大的女人");
}
public void visit(Woman woman) {
System.out.println("當(dāng)女人成功時(shí),背后大多有一個(gè)不成功的男人");
}
}
public class Love implements Visitor{
public void visit(Man man) {
System.out.println("當(dāng)男人戀愛時(shí),凡事不懂也裝懂");
}
public void visit(Woman woman) {
System.out.println("當(dāng)女人戀愛時(shí),遇事懂也裝不懂");
}
}
按照類圖改造一下人的接口與實(shí)現(xiàn)類
Java代碼 收藏代碼
public interface Person {
void accept(Visitor visitor);
}
public class Man implements Person{
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class Woman implements Person{
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
這時(shí)Man與Woman變得輕盈多了,不再需要寫一大段的if,else,只需要按不同的狀態(tài),傳入不同的訪問者,執(zhí)行訪問者的方法就OK了。
為了更好地實(shí)現(xiàn)客戶類與具體元素的解耦,加入一個(gè)ObjectStructure類。有了ObjectStructure能更方便地執(zhí)行一些任何,其具體細(xì)節(jié)對(duì)于客戶端來說是透明的。
import java.util.*;
public class ObjectStructure {
private List<Person> elements = new ArrayList<Person>();
public void attach(Person element){
elements.add(element);
}
public void detach(Person element){
elements.remove(elements);
}
//遍歷各種具體元素并執(zhí)行他們的accept方法
public void display(Visitor visitor){
for(Person p:elements){
p.accept(visitor);
}
}
}
客戶端測(cè)試代碼:
Java代碼 收藏代碼
public class Client {
public static void main(String[] args) {
ObjectStructure o = new ObjectStructure(); //依賴于ObjectStructure
//實(shí)例化具體元素
o.attach(new Man());
o.attach(new Woman());
//當(dāng)成功時(shí)不同元素的不同反映
Visitor success = new Success(); //依賴于抽象的Visitor接口
o.display(success);
//當(dāng)戀愛時(shí)的不同反映
Visitor amativeness = new Love(); //依賴于抽象的Visitor接口
o.display(amativeness);
}
}
這時(shí)客戶端只依賴于ObjectStructure類與Visitor接口,實(shí)現(xiàn)了高度的解耦,具體Visitor實(shí)現(xiàn)類的實(shí)例化與具體元素(Man,Woman)的創(chuàng)建可以通過工廠模式甚至配合properties/xml配置文件創(chuàng)建,無需客戶端關(guān)注。而Man與Gril的創(chuàng)建的代碼也可以放在其他地方,客戶端無需知道,如可以放到ObjectStructure的構(gòu)造函數(shù)中完成
public ObjectStructure(){
attach(new Man());
attach(new Woman());
}
在實(shí)例塊{ }中完成實(shí)例化也可以。這時(shí)如果要增加一種狀態(tài)的不同操作,只需要增加一個(gè)具體訪問者,無需要修改具體元素類Man與Woman。代碼如下,
Java代碼 收藏代碼
public class Fail implements Visitor{
public void visit(Man man) {
System.out.println("當(dāng)男人失敗時(shí),悶頭喝酒,誰也不用勸");
}
public void visit(Woman woman) {
System.out.println("當(dāng)女人失敗時(shí),眼淚汪汪,誰也勸不了");
}
}
修改一下客戶端測(cè)試代碼就OK:
Java代碼 收藏代碼
public class Client {
public static void main(String[] args) {
ObjectStructure o = new ObjectStructure(); //依賴于ObjectStructure
//實(shí)例化具體元素
o.attach(new Man());
o.attach(new Woman());
//當(dāng)成功時(shí)不同元素的不同反映
Visitor success = new Success(); //依賴于抽象的Visitor接口
o.display(success);
//當(dāng)戀愛時(shí)的不同反映
Visitor amativeness = new Love(); //依賴于抽象的Visitor接口
o.display(amativeness);
//當(dāng)失敗時(shí)的不同反映
Visitor fail = new Fail();
o.display(fail);
}
}
結(jié)果顯示:
當(dāng)男人成功時(shí),背后多半有一個(gè)偉大的女人
當(dāng)女人成功時(shí),背后大多有一個(gè)不成功的男人
當(dāng)男人戀愛時(shí),凡事不懂也裝懂
當(dāng)女人戀愛時(shí),遇事懂也裝不懂
當(dāng)男人失敗時(shí),悶頭喝酒,誰也不用勸
當(dāng)女人失敗時(shí),眼淚汪汪,誰也勸不了
現(xiàn)在來讓我們看看訪問者模式的定義與類圖:
訪問者模式定義:表示一個(gè)作用于某個(gè)對(duì)象結(jié)構(gòu)中的各元素的操作。它使可以在不改變各元素的類的前提下定義作用于這些元素的新操作。
訪問者模式的特點(diǎn):
1)優(yōu)點(diǎn):使用了訪問者模式以后,對(duì)于原來的類層次增加新的操作,僅僅需要實(shí)現(xiàn)一個(gè)具體訪問者角色就可以了,而不必修改整個(gè)類層次,使得類層次結(jié)構(gòu)的代碼臃腫難以維護(hù)。而且這樣符合“開閉原則”的要求。而且每個(gè)具體的訪問者角色都對(duì)應(yīng)于一個(gè)相關(guān)操作,因此如果一個(gè)操作的需求有變,那么僅僅修改一個(gè)具體訪問者角色,而不用改動(dòng)整個(gè)類層次。
2)訪問者模式的雙重分派技術(shù)
(1)將具體訪問者作為參數(shù)傳遞給具體元素角色
(2)進(jìn)入具體元素角色后,具體元素角色調(diào)用者作為參數(shù)的具體訪問者的visit方法,同時(shí)將自己(this)作為參數(shù)傳遞進(jìn)行。具體訪問者再根據(jù)參數(shù)的不同來執(zhí)行相應(yīng)的方法
3)前提:開閉原則”的遵循總是片面的。如果系統(tǒng)中的類層次發(fā)生了變化,會(huì)對(duì)訪問者模式產(chǎn)生什么樣的影響呢?你必須修改訪問者接口和每一個(gè)具體訪問者。因此4人組曾經(jīng)提出,訪問者模式適用于數(shù)據(jù)結(jié)構(gòu)相對(duì)穩(wěn)定的系統(tǒng)。
4)適用情況:訪問者模式的目的是要把處理從數(shù)據(jù)結(jié)構(gòu)分離出來。很多系統(tǒng)可以按照算法和數(shù)據(jù)結(jié)構(gòu)分開,如果這樣的系統(tǒng)有比較穩(wěn)定的數(shù)據(jù)結(jié)構(gòu),又有易于變化的算法的話,使用訪問者模式是比較合適的,因?yàn)樵L問者模式似得算法操作的增加變得容易。反之,如果這樣的系統(tǒng)的數(shù)據(jù)結(jié)構(gòu)對(duì)象易于變化,經(jīng)常要有新的數(shù)據(jù)對(duì)象增加進(jìn)來,就不適合使用訪問者模式。
作者:chen.yu
深信服三年半工作經(jīng)驗(yàn),目前就職游戲廠商,希望能和大家交流和學(xué)習(xí),
微信公眾號(hào):編程入門到禿頭 或掃描下面二維碼
零基礎(chǔ)入門進(jìn)階人工智能(鏈接)