深入理解設計模式(15):訪問者模式
一、什麼是訪問者模式
定義:表示一個作用於其物件結構中的各元素的操作,它使你可以在不改變各元素類的前提下定義作用於這些元素的新操作。
可以對定義這麼理解:有這麼一個操作,它是作用於一些元素之上的,而這些元素屬於某一個物件結構。同時這個操作是在不改變各元素類的前提下,在這個前提下定義新操作是訪問者模式精髓中的精髓。
主要解決:穩定的資料結構和易變的操作耦合問題。就是把資料結構和作用於結構上的操作解耦合,使得操作集合可相對自由地演化。
本質:預留通路,回撥實現。它的實現主要就是通過預先定義好呼叫的通路,在被訪問的物件上定義accept方法,在訪問者的物件上定義visit方法;然後在呼叫真正發生的時候,通過兩次分發的技術,利用預先定義好的通路,回撥到訪問者具體的實現上。
二、訪問者模式的結構
Visitor抽象訪問者介面:它定義了對每一個元素(Element)訪問的行為,它的引數就是可以訪問的元素,它的方法個數理論上來講與元素個數(Element的實現類個數)是一樣的,從這點不難看出,訪問者模式要求元素類的個數不能改變(不能改變的意思是說,如果元素類的個數經常改變,則說明不適合使用訪問者模式)。
ConcreteVisitor具體訪問者角色:它需要給出對每一個元素類訪問時所產生的具體行為。
Element抽象節點(元素)角色:它定義了一個接受訪問者(accept)的方法,其意義是指,每一個元素都要可以被訪問者訪問。
ConcreteElement具體節點(元素)角色
ObjectStructure結構物件角色:這個便是定義當中所提到的物件結構,物件結構是一個抽象表述,具體點可以理解為一個具有容器性質或者複合物件特性的類,它會含有一組元素(Element),並且可以迭代這些元素,供訪問者訪問。
三、訪問者模式的使用場景
(1)物件結構比較穩定,但經常需要在此物件結構上定義新的操作。
(2)需要對一個物件結構中的物件進行很多不同的且不相關的操作,而需要避免這些操作“汙染”這些物件的類,也不希望在增加新操作時修改這些類。
四、訪問者模式的優缺點
優點:
1. 訪問者模式使得易於增加新的操作 訪問者使得增加依賴於複雜物件結構的構件的操作變得容易了。僅需增加一個新的訪問者即可在一個物件結構上定義一個新的操作。相反, 如果每個功能都分散在多個類之上的話,定義新的操作時必須修改每一類。
2. 訪問者集中相關的操作而分離無關的操作 相關的行為不是分佈在定義該物件結構的 各個類上,而是集中在一個訪問者中。無關行為卻被分別放在它們各自的訪問者子類中。這 就既簡化了這些元素的類,也簡化了在這些訪問者中定義的演算法。所有與它的演算法相關的數 據結構都可以被隱藏在訪問者中。
缺點:
1. 增加新的 ConcreteElement類很困難
Visitor模式使得難以增加新的 Element的子類。每 新增一個新的 ConcreteElement都要在 Vistor中新增一個新的抽象操作,並在每一個 ConcretVisitor類中實現相應的操作。有時可以在 Visitor中提供一個預設的實現,這一實現可 以被大多數的 ConcreteVisitor繼承,但這與其說是一個規律還不如說是一種例外。
所以在應用訪問者模式時考慮關鍵的問題是系統的哪個部分會經常變化,是作用於物件結構上的演算法呢還是構成該結構的各個物件的類。如果老是有新的 ConcretElement類加入進來的話, Vistor類層次將變得難以維護。在這種情況下,直接在構成該結構的類中定義這些操作可能更容易一些。如果 Element類層次是穩定的,而你不斷地增加操作獲修改演算法,訪問者模式可以幫助你管理這些改動。
2. 破壞封裝
訪問者方法假定ConcreteElement介面的功能足夠強,足以讓訪問者進行它 們的工作。結果是,該模式常常迫使你提供訪問元素內部狀態的公共操作,這可能會破壞它 的封裝性。
五、訪問者模式的實現
抽象訪問者角色:為每一個具體節點都準備了一個訪問操作。
//這裡由於有兩個節點,因此,對應就有兩個訪問操作。 public interface Visitor { /** * 對應於NodeA的訪問操作 */ public void visit(NodeA node); /** * 對應於NodeB的訪問操作 */ public void visit(NodeB node); }
具體訪問者
/** * 具體訪問者VisitorA類 */ public class VisitorA implements Visitor { /** * 對應於NodeA的訪問操作 */ @Override public void visit(NodeA node) { System.out.println(node.operationA()); } /** * 對應於NodeB的訪問操作 */ @Override public void visit(NodeB node) { System.out.println(node.operationB()); } } /** * 具體訪問者VisitorB類 */ public class VisitorB implements Visitor { /** * 對應於NodeA的訪問操作 */ @Override public void visit(NodeA node) { System.out.println(node.operationA()); } /** * 對應於NodeB的訪問操作 */ @Override public void visit(NodeB node) { System.out.println(node.operationB()); } }
抽象節點類
/** * 抽象節點類 */ public abstract class Node { /** * 接受操作 */ public abstract void accept(Visitor visitor); }
具體節點類
/** * 具體節點類NodeA */ public class NodeA extends Node { /** * 接受操作 */ @Override public void accept(Visitor visitor) { visitor.visit(this); } /** * NodeA特有的方法 */ public String operationA() { return "NodeA"; } } /** * 具體節點類NodeB */ public class NodeB extends Node { /** * 接受方法 */ @Override public void accept(Visitor visitor) { visitor.visit(this); } /** * NodeB特有的方法 */ public String operationB() { return "NodeB"; } }
結構物件角色類
/** * 結構物件角色類 * 這個結構物件角色持有一個聚集,並向外界提供add()方法作為對聚集的管理操作。通過呼叫這個方法,可以動態地增加一個新的節點。 */ public class ObjectStructure { private List<Node> nodes = new ArrayList<Node>(); /** * 執行方法操作 */ public void action(Visitor visitor) { for (Node node : nodes) { node.accept(visitor); } } /** * 新增一個新元素 */ public void add(Node node) { nodes.add(node); } }
客戶端程式碼
public static void main(String[] args) { //建立一個結構物件 ObjectStructure os = new ObjectStructure(); //給結構增加一個節點 os.add(new NodeA()); //給結構增加一個節點 os.add(new NodeB()); //建立一個訪問者 Visitor visitor = new VisitorA(); os.action(visitor); }