訪問者模式(Visitor)
訪問者模式(Visitor)
意圖:表示一個作用於某對象結構中的各元素的操作,它使你在不改變各元素的類的前提下定義作用於這些元素的新操作。
應用:作用於編譯器語法樹的語義分析算法。
模式結構:
心得:
訪問者模式是要解決對對象添加新的操作和功能時候,如何盡可能不修改對象的類的一種方法。一般為對象添加功能,是需要向對象添加成員函數。但這里對對象(ConcreteElement)添加了一個統一的接口——accept,來接收一個訪問者對象。如何把對對象的操作移出到類外,正是接收參數(Visitor)的作用。它通過調用Visitor的接口函數visitConcreteElement針對當前對象進行操作,當然,當前對象的指針需要被作為參數傳遞出去,以便對對象狀態進行訪問。這樣,擁有Element集合的對象ObjectStruct只要通過遍歷操作,每次調用對象的accept接口就可以讓對象自動告訴訪問者使用執行什么樣的功能了。當需要為對象擴展功能時,只需要再添加一個訪問者,重定義對每類對象進行訪問的方式就可以了。這里涉及一個雙向分派的概念,即accept操作的調用者(Element)是運行時多態的,而且參數Visitor也是運行時多態的。正是因為如此,才讓用戶可以通過將新添的功能封裝為對象,來實現對對象集合批量的不同操作。
舉例:
這里其實可以把Element想象為編譯器的抽象語法樹節點,ConcreteElement可以看作具體的樹節點,如賦值語句和變量訪問節點。Visitor就可以看作語義分析階段的語義檢查,ConcreteVistor可以看作類型檢查功能和代碼生成功能。這些語義分析的功能顯然不應該和語法樹放在一起,那么把它封裝為訪問者,讓他們為不同的節點生成單獨的分析流程和算法。再在節點對象內部使用統一接口accept調用對應的算法即可,節點內容通過自身的對象指針傳遞給訪問者對象。按照圖中所示關系,我們給出C++代碼如下:
class Visitor;
class Element
{
public:
virtual void accept(Visitor*)= 0;
virtual ~Element(){}
};
// 訪問者基類
class ConcreteElementA;
class ConcreteElementB;
class Visitor
{
public:
virtual void visitConcreteElementA(ConcreteElementA*)= 0;
virtual void visitConcreteElementB(ConcreteElementB*)= 0;
virtual ~Visitor(){}
};
// 具體元素
class ConcreteElementA: public Element
{
public:
virtual void accept(Visitor*v)
{
v->visitConcreteElementA( this); // 雙向分派
}
void operationA()
{
cout<< " 對元素A的處理 "<<endl;
}
};
class ConcreteElementB: public Element
{
public:
virtual void accept(Visitor*v)
{
v->visitConcreteElementB( this);
}
void operationB()
{
cout<< " 對元素B的處理 "<<endl;
}
};
// 具體的訪問者
class ConcreteVisitor1: public Visitor
{
public:
virtual void visitConcreteElementA(ConcreteElementA*ea)
{
cout<< " 訪問者1 ";
ea->operationA();
}
virtual void visitConcreteElementB(ConcreteElementB*eb)
{
cout<< " 訪問者1 ";
eb->operationB();
}
};
class ConcreteVisitor2: public Visitor
{
public:
virtual void visitConcreteElementA(ConcreteElementA*ea)
{
cout<< " 訪問者2 ";
ea->operationA();
}
virtual void visitConcreteElementB(ConcreteElementB*eb)
{
cout<< " 訪問者2 ";
eb->operationB();
}
};
// 管理和遍歷元素集合的高層類
class ObjectStruct
{
list<Element*>data;
public:
void addElement(Element*e)
{
data.push_back(e);
}
void delElement(Element*e)
{
data.remove(e);
}
void dispaly(Visitor*v)
{
for(list<Element*>::iterator it=data.begin();
it!=data.end();++it)
{
(*it)->accept(v);
}
}
~ObjectStruct()
{
for(list<Element*>::iterator it=data.begin();
it!=data.end();++it)
{
delete (*it);
}
}
};
這里需要實現一下ObjectStruct類,因為它提供的對象集合的高層遍歷。用戶對元素的逐個操作被簡化為如下方式:
os.addElement( new ConcreteElementA());
os.addElement( new ConcreteElementB());
os.addElement( new ConcreteElementB());
os.addElement( new ConcreteElementA());
Visitor*v1= new ConcreteVisitor1(); // 創建訪問者1
Visitor*v2= new ConcreteVisitor2();
os.dispaly(v1); // 用訪問者1對元素進行操作【雙向分派】
os.dispaly(v2);
由此看來,只要對象的繼承結構(數據結構)變化不大的情況下,比如不會添加新的類型的節點,使用Visitor模式是非常合適的。用戶只要按需創建合適的訪問者類實現之,然后遍歷集合對象,直接“訪問”就可以了。額外需要說明的一點是,訪問者並不一定讓具體元素類繼承於統一的父類,從訪問者抽象類也能看出,抽象類接口僅僅依賴於具體實現的類。之所以讓它們具有公共的基類主要是還是為了批量操作的方便,即使沒有繼承統一的基類,訪問者模式依然能工作,也能為具體的類添加功能。