起源<o:p></o:p>
Delphi中的Visitor模式在基本Visitor模式进行了扩展。更多Visitor模式的资料请参 [Gam+, pages 331..344].<o:p></o:p>
目的
表示一个作用于某个对象结构的中和元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。<o:p></o:p>
[Gam+, page 331].
动机<o:p></o:p>
考虑一个面向对象的建模工具,比如说‘Rational Rose、ModelMaker’,它将一个模型表示为类和类的成员。
在建模工具上提供了许多操作成员功能,比如:列表类的所有成员、生成类的代码框架、反向工程等。
这些操作大多对不同的成员进行不同的操作。它将成员分成字段(fields)、方法(methods)、
属性(properties)。因些我们必须建立专门处理字段的类,专门处理methods的类等等。成员类的集合当然依赖被编译的语言。但对于一给定语言变化不大。
<o:p></o:p>
<v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"> </v:shapetype> <o:p></o:p>
如图显示了部分成员类的框架。问题产生了,如果我将所有这些操作分散到不同的成员类,
将会导致整个系统难于理解,修改,维护。将类代码生成与类成员检查放在一起,将产生混乱。些外加入新的操作时要重新编译的有的类(至少也重新编译所有的相关的系)。有个办法:你可能独立的增加一个新的操作,并这个成员类独立如作用于其上的操作。
要实现上述两个目标,我们可以将每个类中相关操作包装在一上独立的对象(称为visitor)
并在遍历类成员列表时将此对象传递给当前成员。当一个成员‘接受’ 访问,该成员向访问者发送包含自身信息的请求。该成员请自本身作为一个参数。访问者执行这些操作。
例如:一个不使用访问者的代码生成器可能会通成员类的抽象的方法:TMember.WriteInterfaceCode(Output: TStream)生成代码。每一个成员都会调用WriteInterfaceCode生成适当的输出代码。如果通过访问者来生成代码,则会创建一个TinterfaceCodeVisitor对象,并在成员列表上调用参数为访问对象的AcceptVisitor方法。每一个在员在实现AcceptVisitor将会回调visitor:一个字段将调用访问者的VisitField方法,而一个方法则调用VisitMethod方法。这样,以前类Tfield的WriteInterfaceCode操作现在成为TinterfaceCodeVisitor的VisitField操作。<o:p></o:p>
<o:p></o:p>
为使访问者不仅仅只做代码生成,我们需要所有的成员列表的访问者有一个抽象的父类TmemberVisitor。TmemberVisitor必须为每一个成员定义一种方法。一个需要将成员输出为HTML格式的应用将定义TmemberVisitor新的子类,并不再需要在成员类中增加与特定应用相关的代码。Visitor模式将每个操作封装在一个相关的Visitor中<o:p></o:p>
<o:p></o:p>
<o:p></o:p>
应用
下面的代码演示上面描述的类Tmember的Visitor模式的应用
<o:p></o:p>
type
TMember = class (TObject)
public
procedure AcceptMemberVisitor(Visitor: TMemberVisitor); virtual;
end;
<o:p></o:p>
TField = class (TMember)
public
procedure AcceptMemberVisitor(Visitor: TMemberVisitor); override;
end;
<o:p></o:p>
TMethod = class (TMember)
public
procedure AcceptMemberVisitor(Visitor: TMemberVisitor); override;
end;
<o:p></o:p>
TProperty = class (TMember)
public
procedure AcceptMemberVisitor(Visitor: TMemberVisitor); override;
end;
<o:p></o:p>
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;
<o:p></o:p>
implementation
<o:p></o:p>
{ TMember }
<o:p></o:p>
begin
Visitor.VisitMember(Self);
end;
<o:p></o:p>
{ TField }
procedure TField.AcceptMemberVisitor(Visitor: TMemberVisitor);
begin
end;
<o:p></o:p>
{ TMethod }
procedure TMethod.AcceptMemberVisitor(Visitor: TMemberVisitor);
begin
Visitor.VisitMethod(Self);
end;
<o:p></o:p>
{ TProperty }
procedure TProperty.AcceptMemberVisitor(Visitor: TMemberVisitor);
begin
Visitor.VisitProperty(Self);
end;
<o:p></o:p>
{ TMemberVisitor }
procedure TMemberVisitor.VisitField(Instance: TField);
begin
end;
<o:p></o:p>
procedure TMemberVisitor.VisitMember(Instance: TMember);
begin
end;
<o:p></o:p>
procedure TMemberVisitor.VisitMethod(Instance: TMethod);
begin
end;
<o:p></o:p>
procedure TMemberVisitor.VisitProperty(Instance: TProperty);
begin
end;
<o:p></o:p>
说明:
· TMember, TField, TMethod 和 Tproperty都实现了AcceptMemberVisitor方法. 这些方法都嵌入模式中
· TMemberVisitor 类实现了VisitMember, VisitField等方法。TmemberVisitor是一个抽象的类,它所有的方法由具体的子类实现。
下面是一个简单的代码生成器的实现。
代码介绍:
· TCodeGenerationVisitor 是一个用于实现成员的代码生成器的访问者。
· 访问者定义了一个上下文相关的属性:Output: TTextStream,
· 它必须在VisitXXX调用前被定,如:DrawingVisitor典型的需要一个包括canvas的上下文,来支持画图操作。上下文在遍历整个member对列前赋予了代码生成器。
· 代码生成器将整结的生成的类的所有代码
<o:p></o:p>
要真正的了解Visitor模式,你可执行这个例子 ,并进一步的学习双分派机制: accept/visit.
<o:p></o:p>
unit CodeGenerators;
<o:p></o:p>
interface
<o:p></o:p>
uses Classes, TextStreams;
<o:p></o:p>
type
<o:p></o:p>
TCodeGenerator = class (TObject)
public
procedure Generate(Members: TList; Output: TTextStream);
end;
<o:p></o:p>
implementation
<o:p></o:p>
uses Members;
<o:p></o:p>
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;
<o:p></o:p>
<o:p></o:p>
{ TCodeGenerationVisitor }
procedure TCodeGenerationVisitor.VisitField(Instance: TField);
begin
Output.WriteLnFmt(' %s: %s;', [Instance.Name, Instance.DataName]);
end;
<o:p></o:p>
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;
<o:p></o:p>
procedure TCodeGenerationVisitor.VisitProperty(Instance: TProperty);
begin
Output.WriteLnFmt(' property %s: %s read %s write %s;',
[Instance.Name, Instance.DataName,
Instance.ReadSpecifier, Instance.WriteSpecifier]);
end;
<o:p></o:p>
{ TCodeGenerator }
procedure TCodeGenerator.Generate(Members: TList; Output: TTextStream);
var
I: Integer;
begin
{写入类定义 }
Output.WriteLine('TSample = class (TObject)');
<o:p></o:p>
{好! 加入代码生成器的访问者}
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;