Delphi中的Visitor模式在基本Visitor模式進行了擴充。更多Visitor模式的資料請參[Gam+,pages331..344].
表示一個作用於某個物件結構的中和元素的操作。它使你可以在不改變各元素的類別的前提下定義作用於這些元素的新操作。
[Gam+,page331].
考慮一個物件導向的建模工具,比如說'Rational Rose、ModelMaker',它將一個模型表示為類別和類別的成員。
在建模工具上提供了許多操作成員功能,例如:清單類別的所有成員、生成類別的程式碼框架、反向工程等。
這些操作大多對不同的成員進行不同的操作。它將成員分成字段(fields)、方法(methods)、
屬性(PRoperties)。因些我們必須建立專門處理字段的類,專門處理methods的類等等。成員類別的集合當然依賴被編譯的語言。但對於一給定語言變化不大。
如圖顯示了部分成員類別的框架。問題產生了,如果我將所有這些操作分散到不同的成員類,
將會導致整個系統難於理解,修改,維護。將類別程式碼產生與類別成員檢查放在一起,將產生混亂。一些外加入新的操作時要重新編譯的有的類別(至少也重新編譯所有的相關的系)。有個辦法:你可能獨立的增加一個新的操作,並這個成員類別獨立如作用於其上的操作。
要實現上述兩個目標,我們可以將每個類別中相關操作包裝在一上獨立的物件(稱為visitor )
並在遍歷類別成員清單時將此物件傳遞給目前成員。當一個成員'接受' 訪問,該成員向訪問者發送包含自身信息的請求。該成員請自本身作為一個參數。訪客執行這些操作。
例如:一個不使用訪客的程式碼產生器可能會通成員類別的抽象的方法:TMember.WriteInterfaceCode(Output: TStream)產生程式碼。每一個成員都會呼叫WriteInterfaceCode產生適當的輸出程式碼。如果透過訪客來產生程式碼,則會建立一個TinterfaceCodeVisitor物件,並在成員清單上呼叫參數為存取物件的AcceptVisitor方法。每一個在員在實作AcceptVisitor將會回呼visitor :一個欄位會呼叫訪客的VisitField方法,而一個方法則呼叫VisitMethod方法。這樣,以前類別Tfield的WriteInterfaceCode操作現在成為TinterfaceCodeVisitor的VisitField操作。
為使訪客不僅僅只做程式碼生成,我們需要所有的成員清單的訪客有一個抽象的父類TmemberVisitor。 TmemberVisitor必須為每一個成員定義一種方法。一個需要將成員輸出為HTML格式的應用程式將定義TmemberVisitor新的子類,並不再需要在成員類別中增加與特定應用程式相關的程式碼。 Visitor模式將每個操作封裝在一個相關的Visitor中
使用Visitor模式,必須定義兩個層次的類別:一個應於接受操作的元素(Tmember層次)另一個定義於對元素的操作(TmemberVisitor 層次)。增加一個新的操作時只需為訪客層級增加一個新的子類別。我可能簡單的定義新的TmemberVisitor子類別以增加新的功能。
下面的程式碼示範上面描述的類別Tmember的Visitor模式的應用
type
TMember = class (TObject)
public
procedure AcceptMemberVisitor(Visitor: TMemberVisitor); virtual;
end;
TField = class (TMember)
public
procedure AcceptMemberVisitor(Visitor: TMemberVisitor); override;
end;
TMethod = class (TMember)
public
procedure AcceptMemberVisitor(Visitor: TMemberVisitor); override;
end;
TProperty = class (TMember)
public
procedure AcceptMemberVisitor(Visitor: TMemberVisitor); override;
end;
TMemberVisitor = class (TObject)
public
procedure VisitField(Instance: TField); virtual;
procedure VisitMember(Instance: TMember); virtual;
procedure VisitMethod(Instance: TMethod); virtual;
procedure VisitProperty(Instance: TProperty); virtual;
end;
implementation
{ TMember }
begin
Visitor.VisitMember(Self);
end;
{ TField }
procedure TField.AcceptMemberVisitor(Visitor: TMemberVisitor);
begin
end;
{ TMethod }
procedure TMethod.AcceptMemberVisitor(Visitor: TMemberVisitor);
begin
Visitor.VisitMethod(Self);
end;
{ TProperty }
procedure TProperty.AcceptMemberVisitor(Visitor: TMemberVisitor);
begin
Visitor.VisitProperty(Self);
end;
{ TMemberVisitor }
procedure TMemberVisitor.VisitField(Instance: TField);
begin
end;
procedure TMemberVisitor.VisitMember(Instance: TMember);
begin
end;
procedure TMemberVisitor.VisitMethod(Instance: TMethod);
begin
end;
procedure TMemberVisitor.VisitProperty(Instance: TProperty);
begin
end;
說明:
?TMember, TField, TMethod 和Tproperty都實作了AcceptMemberVisitor方法. 這些方法都嵌入模式中
?TMemberVisitor 類別實作了VisitMember, VisitField等方法。 TmemberVisitor是一個抽象的類,它所有的方法都由具體的子類別實作。
下面是一個簡單的程式碼產生器的實作。
程式碼介紹:
? TCodeGenerationVisitor 是用來實作成員的程式碼產生器的訪客。
? 訪客定義了一個上下文相關的屬性:Output: TTextStream,
? 它必須在VisitXXX呼叫前被定,如:DrawingVisitor典型的需要一個包含canvas的上下文,來支援畫圖操作。上下文在遍歷整個member對列前賦予了程式碼產生器。
?程式碼產生器將整結的生成的類別的所有程式碼
要真正的了解Visitor模式,你可執行這個例子,並進一步的學習雙分派機制: accept/visit.
unit CodeGenerators;
interface
uses Classes, TextStreams;
type
TCodeGenerator = class (TObject)
public
procedure Generate(Members: TList; Output: TTextStream);
end;
implementation
uses Members;
type
TCodeGenerationVisitor = class (TMemberVisitor)
private
FOutput: TTextStream;
public
procedure VisitField(Instance: TField); override;
procedure VisitMethod(Instance: TMethod); override;
procedure VisitProperty(Instance: TProperty); override;
property Output: TTextStream read FOutput write FOutput;
end;
{ TCodeGenerationVisitor }
procedure TCodeGenerationVisitor.VisitField(Instance: TField);
begin
Output.WriteLnFmt(' %s: %s;', [Instance.Name, Instance.DataName]);
end;
procedure TCodeGenerationVisitor.VisitMethod(Instance: TMethod);
var
MKStr, DTStr: string;
begin
case Instance.MethodKind of
mkConstructor: MKStr := 'constructor';
mkDestructor: MKStr := 'destructor';
mkProcedure: MKStr := 'procedure';
mkFuntion: MKStr := 'function';
end;
if Instance.MethodKind = mkFunction then
DTStr := ': ' + Instance.DataName
else
DTStr := ';
{程式碼不完整,現足以示範Tmethod程式碼產生}
Output.WriteLnFmt(' %s %s%s%s;'
[MKStr, Instance.Name, Instance.Parameters, DTStr]);
end;
procedure TCodeGenerationVisitor.VisitProperty(Instance: TProperty);
begin
Output.WriteLnFmt(' property %s: %s read %s write %s;',
[Instance.Name, Instance.DataName,
Instance.ReadSpecifier, Instance.WriteSpecifier]);
end;
{ TCodeGenerator }
procedure TCodeGenerator.Generate(Members: TList; Output: TTextStream);
var
I: Integer;
begin
{寫入類別定義}
Output.WriteLine('TSample = class (TObject)');
{好! 加入程式碼產生器的訪客}
Visitor := TCodeGenerationVisitor.Create;
Try
{記住為訪問都提供上下文,以便更好的訪問VisitXXX方法。 }
for I := 0 to Members.Count - 1 do
{ 程式碼的具體段,好事情發生了}
TMember(Members[I]).AcceptMemberVisitor(Visitor);
finally
Visitor.Free;
end;
{類別成員的程式碼生成完畢}
Output.WriteLine('end;');
end;
正在組織
//很多摘自《設計模式》,