問題
在物件導向系統的開發和設計過程中,經常會遇到一種情況就是需求變更,經常我們做好的一個設計,實現了一個系統原型,咱們的客戶又會有了新的需求。我們又因此不得不去修改已有的設計,最常見的就是解決方案就是給已經設計實現好的類添加新的方法去實現客戶的需求,這樣就陷入了設計變更的夢魔:不停的打補丁,其帶來的後果就是設計根本就不可能封閉,編譯永遠都是整個系統代碼。
visitor模式則提供了一種解決方案:將更新封裝到一個類中(訪問操作),並由待更改類提供一個接受介面,則可達到效果。
visitor訪問者模式
表示一個作用於某對象結構中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。
適用於資料結構穩定的系統。它把資料結構和作用於資料結構上的操作分離開,使得操作集合。
優點:新增加操作很容易,因為增加新操作就相當於增加一個訪問者,訪問者模式將有關的行為集中到一個訪問者對象中。
解析:visitor模式把對結點的訪問封裝成一個抽象基類,通過派生出不同的子類生產新的訪問方式。在實現的時候,在visitor抽象基類中聲明了對所有不同結點進行訪問的介面函數。這樣也造成了visitor模式的一個缺陷:新加入一個結點的時候都要添加visitor中的對其進行提供者函數,這樣使得所有的visitor及其衍生類別都要重新編譯了,也就是說visitor模式一個缺點就是添加新的結點十分困難。另外,還需要指出的是visitor模式採用了所謂的”雙指派”技術。
小demo
visitor.h
#ifndef VISITOR_H#define VISITOR_Hclass Visitor;class Element{public:virtual ~Element(){}virtual void Accept(Visitor &rVisitor) = 0;protected:Element(){}};class ConcreteElementA : public Element{public:virtual ~ConcreteElementA() {}virtual void Accept(Visitor &rVisitor);};class ConcreteElementB : public Element{public:virtual ~ConcreteElementB() {}virtual void Accept(Visitor &rVisitor);};class Visitor{public:virtual ~Visitor(){}virtual void VisitConcreteElementA(Element *elm) = 0;virtual void VisitConcreteElementB(Element *elm) = 0;protected:Visitor(){}};class ConcreteVisitorA: public Visitor{public:virtual ~ConcreteVisitorA(){}virtual void VisitConcreteElementA(Element *elm);virtual void VisitConcreteElementB(Element *elm);};class ConcreteVisitorB: public Visitor{public:virtual ~ConcreteVisitorB(){}virtual void VisitConcreteElementA(Element *elm);virtual void VisitConcreteElementB(Element *elm);};#endif
visitor.cpp
#include "Visitor.h"#include <iostream>void ConcreteElementA::Accept(Visitor &rVisitor){rVisitor.VisitConcreteElementA(this); std::cout<<"visiting ConcreteElementA\n";}void ConcreteElementB::Accept(Visitor &rVisitor){ rVisitor.VisitConcreteElementB(this); std::cout<<"visiting ConcreteElementB\n";}void ConcreteVisitorA::VisitConcreteElementA(Element *elm){std::cout << "VisitConcreteElementA By ConcreteVisitorA\n";}void ConcreteVisitorA::VisitConcreteElementB(Element *elm){std::cout << "VisitConcreteElementB By ConcreteVisitorA\n";}void ConcreteVisitorB::VisitConcreteElementA(Element *elm){std::cout << "VisitConcreteElementA By ConcreteVisitorB\n";}void ConcreteVisitorB::VisitConcreteElementB(Element *elm){std::cout << "VisitConcreteElementB By ConcreteVisitorB\n";}
main.cpp
#include "Visitor.h"#include <windows.h>int main(){Visitor *pVisitorA = new ConcreteVisitorA();Element *pElement = new ConcreteElementA();pElement->Accept(*pVisitorA);delete pElement;delete pVisitorA; system("pause");return 0;}
visitor模式在不破壞類的前提下,為類提供新的操作。visitor模式的關鍵是”雙指派技術”。C++語言支援的是單指派。
雙指派技術:雙指派意味著執行的操作將取決於請求的種類和接收者的類型。
在visitor模式中Accept()操作是一個雙指派的操作。具體調用哪一個具體的Accept()操作,有兩個決定因素:1.Element類型。因為Accept()是多態的操作,需要具體的Element類型的子類才可以決定到底調用哪一個Accept()實現;2.Visitor的類型。Accept()操作有一個參數(Visitor &vis),要決定實際傳進來的visitor的實體類別才可以決定具體調用哪一個visitConcrete()實現。
有時候我們需要為Element提供更多的修改,這樣我們就可以通過為Element提供一系列的Visitor模式可以使得Element在不修改自己的同時增加新的操作,但是這帶來了一個顯著問題:ConcreteElement的擴充很困難:每增加一個Element的子類,就要修改Visitor的介面,使得可以提供給這個新增加的子類的訪問機制。