目錄
- 前言
- 問題
- 解決方案
- 結構
- 代碼
前言
訪問者是一種行為設計模式,它能將算法與其所作用的對象隔離開來。
問題
假如你的團隊開發了一款能夠使用巨型圖像中地理信息的應用程序。 圖像中的每個節點既能代表復雜實體(例如一座城市), 也能代表更精細的對象(例如工業區和旅游景點等)。如果節點代表的真實對象之間存在公路, 那么這些節點就會相互連接。 在程序內部, 每個節點的類型都由其所屬的類來表示,每個特定的節點則是一個對象。
一段時間后, 你接到了實現將圖像導出到 XML 文件中的任務。 這些工作最初看上去非常簡單。 你計劃為每個節點類添加導出函數, 然后遞歸執行圖像中每個節點的導出函數。 解決方案簡單且優雅: 使用多態機制可以讓導出方法的調用代碼不會和具體的節點類相耦合。
但你不太走運,系統架構師拒絕批準對已有節點類進行修改。他認為這些代碼已經是產品了, 不想冒險對其進行修改, 因為修改可能會引入潛在的缺陷。
此外,他還質疑在節點類中包含導出 XML 文件的代碼是否有意義。 這些類的主要工作是處理地理數據。 導出 XML 文件的代碼放在這里并不合適。
還有另一個原因, 那就是在此項任務完成后, 營銷部門很有可能會要求程序提供導出其他類型文件的功能, 或者提出其他奇怪的要求。 這樣你很可能會被迫再次修改這些重要但脆弱的類。
解決方案
訪問者模式建議將新行為放入一個名為訪問者的獨立類中,而不是試圖將其整合到已有類中。 現在, 需要執行操作的原始對象將作為參數被傳遞給訪問者中的方法, 讓方法能訪問對象所包含的一切必要數據。
如果現在該操作能在不同類的對象上執行會怎么樣呢? 比如在我們的示例中,各節點類導出 XML 文件的實際實現很可能會稍有不同。 因此, 訪問者類可以定義一組(而不是一個)方法,且每個方法可接收不同類型的參數。
但我們究竟應該如何調用這些方法(尤其是在處理整個圖像方面)呢?這些方法的簽名各不相同,因此我們不能使用多態機制。 為了可以挑選出能夠處理特定對象的訪問者方法,我們需要對它的類進行檢查。這是不是聽上去像個噩夢呢?
你可能會問, 我們為什么不使用方法重載呢? 就是使用相同的方法名稱,但它們的參數不同。不幸的是,即使我們的編程語言(例如 Java 和 C#) 支持重載也不行。 由于我們無法提前知曉節點對象所屬的類, 所以重載機制無法執行正確的方法。方法會將 節點 基類作為輸入參數的默認類型。
但是, 訪問者模式可以解決這個問題。 它使用了一種名為雙分派的技巧,不使用累贅的條件語句也可下執行正確的方法。與其讓客戶端來選擇調用正確版本的方法, 不如將選擇權委派給作為參數傳遞給訪問者的對象。 由于該對象知曉其自身的類, 因此能更自然地在訪問者中選出正確的方法。 它們會“接收”一個訪問者并告訴其應執行的訪問者方法。
我承認最終還是修改了節點類, 但畢竟改動很小, 且使得我們能夠在后續進一步添加行為時無需再次修改代碼。
現在, 如果我們抽取出所有訪問者的通用接口, 所有已有的節點都能與我們在程序中引入的任何訪問者交互。 如果需要引入與節點相關的某個行為, 你只需要實現一個新的訪問者類即可。
結構
代碼
#include <iostream>
#include <memory>
using namespace std;class FactoryElement;
class HouseElement;
class Visitor{
public:virtual void visit(FactoryElement* facElemPtr)=0;virtual void visit(HouseElement* houElemPtr)=0;virtual ~Visitor(){}
};class Element{
public:virtual void accept(shared_ptr<Visitor> v)=0;virtual ~Element(){}
};
class FactoryElement:public Element{
public:void accept(shared_ptr<Visitor> v) override{v->visit(this);}void featureFac(){cout<<"這是工廠區"<<endl;}
};
class HouseElement:public Element{
public:void accept(shared_ptr<Visitor> v) override{v->visit(this);}void featureHou(){cout<<"這是居民區"<<endl;}
};class XmlVisitor:public Visitor{
public:void visit(FactoryElement* facElemPtr) override{facElemPtr->featureFac();cout<<"導出為xml文件"<<endl;} void visit(HouseElement* houElemPtr) override{houElemPtr->featureHou();cout<<"導出為xml文件"<<endl;}
};
class JsonVisitor:public Visitor{
public:void visit(FactoryElement* facElemPtr) override{facElemPtr->featureFac();cout<<"導出為json文件"<<endl;} void visit(HouseElement* houElemPtr) override{houElemPtr->featureHou();cout<<"導出為json文件"<<endl;}
};int main(){auto xmlVisitor=make_shared<XmlVisitor>();auto jsonVisitor=make_shared<JsonVisitor>();auto facElem = make_unique<FactoryElement>();auto houElem = make_unique<HouseElement>();facElem->accept(xmlVisitor);houElem->accept(xmlVisitor);facElem->accept(jsonVisitor);houElem->accept(jsonVisitor);return 0;
}