訪問者模式屬于行為型模式;指將作用于某種資料結構中各元素的操作分離出來封裝成獨立的類,使其在不改變資料結構的前提下可以添加作用于這些元素的新的操作,為資料結構中的每個元素提供多種訪問方式,它將對資料的操作與資料結構進行分離,是行為類模式中最復雜的一種模式,
訪問者模式的目的是封裝一些施加于某種資料結構元素之上的操作,一旦這些操作需要修改的話,接受這個操作的資料結構則可以保持不變,
雙重分派
資料結構的每一個節點都可以接受一個訪問者的呼叫,此節點向訪問者物件傳入節點物件,而訪問者物件則反過來執行節點物件的操作,這樣的程序叫做“雙重分派”,節點呼叫訪問者,將它自己傳入,訪問者則將某演算法針對此節點執行,
雙重分派意味著施加于節點之上的操作是基于訪問者和節點本身的資料型別,而不僅僅是其中的一者,
單分派和多分派
方法的接收者和方法的引數統稱為方法的宗量, 根據分派基于宗量多少(接收者是一個宗量,引數是一個宗量),可以將分派分為單分派和多分派,單分派是指根據一個宗量就可以知道呼叫目標(即應該呼叫哪個方法),多分派需要根據多個宗量才能確定呼叫目標,
訪問者模式的UML類圖如下:

從上圖可以看出,訪問者模式涉及到抽象訪問者角色、具體訪問者角色、抽象節點角色、具體節點角色、結構物件角色以及客戶端角色等6個角色:
- 抽象訪問者(Visitor)角色:宣告了一個或者多個訪問操作,形成所有的具體元素角色必須實作的介面,
- 具體訪問者(ConcreteVisitor)角色:實作抽象訪問者角色所宣告的介面,也就是抽象訪問者所宣告的各個訪問操作,
- 抽象節點(Node)角色:宣告一個接受操作,接受一個訪問者物件作為一個參量,
- 具體節點(Node)角色:實作了抽象元素所規定的接受操作,
- 結構物件(ObjectStructure)角色:有如下的一些責任,可以遍歷結構中的所有元素;如果需要,提供一個高層次的介面讓訪問者物件可以訪問每一個元素;如果需要,可以設計成一個復合物件或者一個聚集,如列(List)或集合(Set),
從上圖可以看到,抽象訪問者角色為每一個具體節點都準備了一個訪問操作,由于有兩個節點,因此,對應的就有兩個訪問操作,
投票例子
我們都看過很多歌唱選秀類的綜藝節目,當某個表演者表演完畢后,下面的評委需要對其表演進行評價(晉級,淘汰,待定),決定其是否晉級下一輪,我們這里就以這個為例子講解訪問者設計模式,
例子的UML類圖:

抽象節點角色:
package com.charon.visitor;
/**
* @className: Person
* @description:
* @author: charon
* @create: 2022-03-27 17:18
*/
public abstract class Person {
abstract void accept(Action action);
}
具體節點角色:
package com.charon.visitor;
/**
* @className: Man
* @description:
* @author: charon
* @create: 2022-03-27 17:19
*/
public class Man extends Person {
private String name;
public Man(String name) {
this.name = name;
}
/**
* Gets the value of name
*
* @return the value of name
*/
public String getName() {
return name;
}
@Override
void accept(Action action) {
action.getManResult(this);
}
}
package com.charon.visitor;
/**
* @className: Woman
* @description:
* @author: charon
* @create: 2022-03-27 17:19
*/
public class Woman extends Person{
private String name;
public Woman(String name) {
this.name = name;
}
/**
* Gets the value of name
*
* @return the value of name
*/
public String getName() {
return name;
}
@Override
void accept(Action action) {
action.getWomanResult(this);
}
}
抽象訪問者角色:
package com.charon.visitor;
/**
* @className: Action
* @description:
* @author: charon
* @create: 2022-03-27 17:19
*/
public abstract class Action {
/**
* 得到男人的評價結果
* @param man
*/
abstract void getManResult(Man man);
/**
* 得到女人的評價結果
* @param woman
*/
abstract void getWomanResult(Woman woman);
}
具體訪問者角色:
package com.charon.visitor;
/**
* @className: FailAction
* @description:
* @author: charon
* @create: 2022-03-27 17:21
*/
public class FailAction extends Action{
@Override
void getManResult(Man man) {
System.out.println(man.getName() + " 給的評價是該表演者表演淘汰,,,,");
}
@Override
void getWomanResult(Woman woman) {
System.out.println(woman.getName() + " 給的評價是該表演者表演淘汰,,,,");
}
}
package com.charon.visitor;
/**
* @className: SuccessAction
* @description:
* @author: charon
* @create: 2022-03-27 17:22
*/
public class SuccessAction extends Action{
@Override
void getManResult(Man man) {
System.out.println(man.getName() + " 給的評價是該表演者表演晉級,,,,");
}
@Override
void getWomanResult(Woman woman) {
System.out.println(woman.getName() + " 給的評價是該表演者表演晉級,,,,");
}
}
結構物件角色:
package com.charon.visitor;
import java.util.ArrayList;
import java.util.List;
/**
* @className: ObjectStructure
* @description:
* @author: charon
* @create: 2022-03-27 17:25
*/
public class ObjectStructure {
/**
* 集合,用于存放表演的評價者
*/
private List<Person> persons = new ArrayList<>();
public void add(Person person){
persons.add(person);
}
public void remove(Person person){
persons.remove(person);
}
/**
* 顯示評價結果
* @param action
*/
public void display(Action action){
for (Person person : persons) {
person.accept(action);
}
}
}
測驗:
package com.charon.visitor;
/**
* @className: Client
* @description:
* @author: charon
* @create: 2022-03-27 17:29
*/
public class Client {
public static void main(String[] args) {
ObjectStructure structure = new ObjectStructure();
structure.add(new Man("男評委1"));
structure.add(new Man("男評委2"));
structure.add(new Woman("女評委1"));
structure.add(new Woman("女評委2"));
structure.display(new SuccessAction());
}
}
列印:
男評委1 給的評價是該表演者表演晉級,,,,
男評委2 給的評價是該表演者表演晉級,,,,
女評委1 給的評價是該表演者表演晉級,,,,
女評委2 給的評價是該表演者表演晉級,,,,
如上所示,訪問者模式的代碼就完成了,如果現在需要添加一個待定的操作型別,就只需要添加一個WaitAction就行了:
package com.charon.visitor;
/**
* @className: WaitAction
* @description:
* @author: charon
* @create: 2022-03-27 17:39
*/
public class WaitAction extends Action{
@Override
void getManResult(Man man) {
System.out.println(man.getName() + " 給的評價是該表演者表演待定,,,,");
}
@Override
void getWomanResult(Woman woman) {
System.out.println(woman.getName() + " 給的評價是該表演者表演待定,,,,");
}
}
訪問者模式的優點如下:
- 擴展性好,能夠在不修改物件結構中的元素的情況下,為物件結構中的元素添加新的功能,
- 復用性好,可以通過訪問者來定義整個物件結構通用的功能,從而提高系統的復用程度,
- 靈活性好,訪問者模式將資料結構與作用于結構上的操作解耦,使得操作集合可相對自由地演化而不影響系統的資料結構,
- 符合單一職責原則,訪問者模式把相關的行為封裝在一起,構成一個訪問者,使每一個訪問者的功能都比較單一,
訪問者模式的缺點如下:
- 增加新的元素類很困難,在訪問者模式中,每增加一個新的元素類,都要在每一個具體訪問者類中增加相應的具體操作,這違背了“開閉原則”,
- 破壞封裝,訪問者模式中具體元素對訪問者公布細節,這破壞了物件的封裝性,
- 違反了依賴倒置原則,訪問者模式依賴了具體類,而沒有依賴抽象類,
由于訪問者模式的這些缺點,導致很多人反對使用訪問者模式,
訪問者模式的應用場景
當系統中存在型別數量穩定(固定)的一類資料結構時,可以使用訪問者模式方便地實作對該型別所有資料結構的不同操作,而又不會對資料產生任何副作用(臟資料),簡而言之,就是當對集合中的不同型別資料(型別數量穩定)進行多種操作時,使用訪問者模式,
通常在以下情況可以考慮使用訪問者模式,
- 物件結構相對穩定,但其操作演算法經常變化的程式,
- 物件結構中的物件需要提供多種不同且不相關的操作,而且要避免讓這些操作的變化影響物件的結構,
- 物件結構包含很多型別的物件,希望對這些物件實施一些依賴于其具體型別的操作,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/450417.html
標籤:其他
上一篇:設計模式之迭代器模式
