生老病死乃常態,是咱們每一個人都逃脫不了的,因此進醫院就是一件再日常不過的事情了。在醫院看病,你首先的掛號,而後找到主治醫生,醫生呢?先給你稍微檢查下,而後就是各類處方單(什麼驗血、CD、B超等等,太坑了。。。。),再而後就給你一個處方單要你去拿藥。拿藥咱們能夠分爲兩步走,第一步,咱們要去交錢,劃價人員會根據你的處方單上面的藥進行劃價,交錢。第二步,去藥房拿藥,藥房工做者一樣根據你的處方單給你相對應的藥。java
這裏咱們就劃價和拿藥兩個步驟進行討論。這裏有三個類,處方單(藥)、劃價人員、藥房工做者。同時劃價人員和藥房工做者都各自有一個動做:劃價、拿藥。這裏進行最初步的設計以下:算法
劃價人員public class Charge { public void action(){ public void action(){ if("A藥".equals(medicine)){ //A的價格 } if("B藥".equals(medicine)){ //B的價格 } if("C藥".equals(medicine)){ //C的價格 } if("D藥".equals(medicine)){ //D的價格 } if("E藥".equals(medicine)){ //E的價格 } ............ } } }
藥房工做者設計模式
public class WorkerOfPharmacy { public void action(){ if("A藥".equals(medicine)){ //給你A藥 } if("B藥".equals(medicine)){ //給你B藥 } if("C藥".equals(medicine)){ //給你C藥 } if("D藥".equals(medicine)){ //給你D藥 } if("E藥".equals(medicine)){ //給你E藥 } ............ } }
看到這樣的代碼,咱們第一個想法就是,這TMD太亂來了吧,這麼多的if…else,誰看了不頭暈,並且咱們能夠想象醫院裏面的藥是那麼多,並且隨時都會增長的,增長了藥就要改變劃價人員和藥房工做者的代碼,這是咱們最不但願改變的。那麼有沒有辦法來解決呢?有,訪問者模式提供一中比較好的解決方案。數據結構
在咱們實際的軟件開發過程當中,有時候咱們對同一個對象可能會有不一樣的處理,對相同元素對象也可能存在不一樣的操做方式,如處方單,劃價人員要根據它來劃價,藥房工做者要根據它來給藥。並且可能會隨時增長新的操做,如醫院增長新的藥物。可是這裏有兩個元素是保持不變的,或者說不多變:劃價人員和藥房工做中,變的只不過是他們的操做。因此咱們想若是可以將他們的操做抽象化就行了。這裏訪問者模式就是一個值得考慮的解決方案了。this
訪問者模式的目的是封裝一些施加於某種數據結構元素之上的操做,一旦這些操做須要修改的話,接受這個操做的數據結構能夠保持不變。爲不一樣類型的元素提供多種訪問操做方式,且能夠在不修改原有系統的狀況下增長新的操做方式,這就是訪問者模式的模式動機。spa
訪問者模式即表示一個做用於某對象結構中的各元素的操做,它使咱們能夠在不改變各元素的類的前提下定義做用於這些元素的新操做。設計
首先咱們要明確一點就是訪問者模式適用於數據結構相對穩定的系統。它是將數據的操做與數據結構進行分離了,若是某個系統的數據結構相對穩定,可是操做算法易於變化的話,就比較適用適用訪問者模式,由於訪問者模式使得算法操做的增長變得比較簡單了。code
下圖是訪問者模式的UML結構圖:對象
訪問者模式主要包含以下幾個角色:blog
Vistor: 抽象訪問者。爲該對象結構中的ConcreteElement的每個類聲明的一個操做。
ConcreteVisitor: 具體訪問者。實現Visitor申明的每個操做,每個操做實現算法的一部分。
Element: 抽象元素。定義一個Accept操做,它以一個訪問者爲參數。
ConcreteElement: 具體元素 。實現Accept操做。
ObjectStructure: 對象結構。可以枚舉它的元素,能夠提供一個高層的接口來容許訪問者訪問它的元素。
在訪問者模式中對象結構存儲了不一樣類型的對象,以便不一樣的訪問者來訪問。從上面的UML結構圖中咱們能夠看出,訪問者模式主要分爲兩個層次結構,一個是訪問者層次結構,提供了抽象訪問者和具體訪問者,主要用於什麼一些操做。一個是元素層次結構,提供了抽象元素和具體元素,主要用於聲明Accept操做。
在訪問者模式中相同的訪問者能夠以不一樣的方式訪問不一樣的元素,因此在訪問者模式中增長新的訪問者無需修改現有代碼,可擴展行強。
同時在訪問者模式用到了一種雙分派的技術,所謂雙分派技術就是在選擇一個方法的時候,不只僅要根據消息接收者(receiver)的運行時區別(Run time type),還要根據參數的運行時區別。在訪問者模式中,客戶端將具體狀態當作參數傳遞給具體訪問者,這裏完成第一次分派,而後具體訪問者做爲參數的「具體狀態」中的方法,同時也將本身this做爲參數傳遞進去,這裏就完成了第二次分派。雙分派意味着獲得的執行操做決定於請求的種類和接受者的類型。
一樣以上面在醫院付費、取藥爲實例。在這個實例中劃價員和藥房工做者做爲訪問者,藥品做爲訪問元素、處方單做爲對象結構,因此整個UML結構圖以下:
抽象訪問者:Visitor.java
public abstract class Visitor { protected String name; public void setName(String name) { this.name = name; } public abstract void visitor(MedicineA a); public abstract void visitor(MedicineB b); }
具體訪問者:劃價員、Charger.java
public class Charger extends Visitor{ public void visitor(MedicineA a) { System.out.println("劃價員:" + name +"給藥" + a.getName() +"劃價:" + a.getPrice()); } public void visitor(MedicineB b) { System.out.println("劃價員:" + name +"給藥" + b.getName() +"劃價:" + b.getPrice()); } }
具體訪問者:藥房工做者、WorkerOfPharmacy.java
public class WorkerOfPharmacy extends Visitor{ public void visitor(MedicineA a) { System.out.println("藥房工做者:" + name + "拿藥 :" + a.getName()); } public void visitor(MedicineB b) { System.out.println("藥房工做者:" + name + "拿藥 :" + b.getName()); } }
抽象元素:Medicine.java
public abstract class Medicine { protected String name; protected double price; public Medicine (String name,double price){ this.name = name; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public abstract void accept(Visitor visitor); }
具體元素:MedicineA.java
public class MedicineA extends Medicine{ public MedicineA(String name, double price) { super(name, price); } public void accept(Visitor visitor) { visitor.visitor(this); } }
具體元素:MedicineB.java
public class MedicineB extends Medicine{ public MedicineB(String name, double price) { super(name, price); } public void accept(Visitor visitor) { visitor.visitor(this); } }
public class Presciption { List<Medicine> list = new ArrayList<Medicine>(); public void accept(Visitor visitor){ Iterator<Medicine> iterator = list.iterator(); while (iterator.hasNext()) { iterator.next().accept(visitor); } } public void addMedicine(Medicine medicine){ list.add(medicine); } public void removeMedicien(Medicine medicine){ list.remove(medicine); } }
public class Client { public static void main(String[] args) { Medicine a = new MedicineA("板藍根", 11.0); Medicine b = new MedicineB("感康", 14.3); Presciption presciption = new Presciption(); presciption.addMedicine(a); presciption.addMedicine(b); Visitor charger = new Charger(); charger.setName("張三"); Visitor workerOfPharmacy = new WorkerOfPharmacy(); workerOfPharmacy.setName("李四"); presciption.accept(charger); System.out.println("-------------------------------------"); presciption.accept(workerOfPharmacy); } }
運行結果
一、使得新增新的訪問操做變得更加簡單。
二、可以使得用戶在不修改現有類的層次結構下,定義該類層次結構的操做。
三、將有關元素對象的訪問行爲集中到一個訪問者對象中,而不是分散搞一個個的元素類中。
一、增長新的元素類很困難。在訪問者模式中,每增長一個新的元素類都意味着要在抽象訪問者角色中增長一個新的抽象操做,並在每個具體訪問者類中增長相應的具體操做,違背了「開閉原則」的要求。
二、破壞封裝。當採用訪問者模式的時候,就會打破組合類的封裝。
三、比較難理解。貌似是最難的設計模式了。
一、對象結構中對象對應的類不多改變,但常常須要在此對象結構上定義新的操做。
二、須要對一個對象結構中的對象進行不少不一樣的而且不相關的操做,而須要避免讓這些操做「污染」這些對象的類,也不但願在增長新操做時修改這些類。
一、訪問者模式封裝了對象結構元素之上的操做,使得新增元素的操做變得很是簡單。因此它比較適用於那麼對象結構不多變化的類。
二、訪問者模式中對象結構存儲了不一樣類型的元素對象,以供不一樣訪問者訪問。