The Visitor mode in Delphi extends the basic Visitor mode. For more information about Visitor mode, please refer to [Gam+, pages331..344].
Represents an operation that operates on neutralized elements of an object structure. It allows you to define new operations that act on each element without changing its class.
[Gam+,page331].
Consider an object-oriented modeling tool, such as 'Rational Rose, ModelMaker', which represents a model as classes and class members.
The modeling tool provides many functions for operating members, such as: listing all members of the class, generating the code framework of the class, reverse engineering, etc.
Most of these operations perform different operations on different members. It divides members into fields, methods,
Properties (PRoperties). Therefore, we must create classes that specifically handle fields, classes that specifically handle methods, and so on. The collection of member classes is of course dependent on the language being compiled. But not much changes for a given language.
The figure shows the framework of some member classes. The problem arises, if I spread all these operations to different member classes,
It will make the entire system difficult to understand, modify, and maintain. Putting class code generation together with class member checking creates confusion. Some classes need to be recompiled when adding new operations (at least all related systems must be recompiled as well). There is a way: you could add a new operation independently of the member class as the operation that acts on it.
To achieve the above two goals, we can wrap the relevant operations in each class in an independent object (called visitor )
And pass this object to the current member when iterating through the class members list. When a member 'accepts' access, the member sends a request containing its own information to the visitor. The member takes itself as a parameter. Visitors perform these actions.
For example: a code generator that does not use visitors may generate code through the abstract method of the member class: TMember.WriteInterfaceCode(Output: TStream). Each member will call WriteInterfaceCode to generate the appropriate output code. If the code is generated through a visitor, a TinterfaceCodeVisitor object will be created and the AcceptVisitor method with the parameter being the access object will be called on the member list. Each member implementing AcceptVisitor will call back the visitor : a field will call the visitor's VisitField method, and a method will call the VisitMethod method. In this way, the WriteInterfaceCode operation of the previous class Tfield now becomes the VisitField operation of TinterfaceCodeVisitor.
To make the visitor do more than just code generation, we need all member list visitors to have an abstract parent class TmemberVisitor. TmemberVisitor must define a method for each member. An application that needs to output members to HTML format will define a new subclass of TmemberVisitor and no longer need to add application-specific code to the member class. The Visitor pattern encapsulates each operation in a related Visitor
Using the Visitor pattern, two levels of classes must be defined: one corresponding to the elements that accept operations (Tmember level) and the other defined for operations on elements (TmemberVisitor level). When adding a new operation, just add a new subclass to the visitor hierarchy. I might simply define a new TmemberVisitor subclass to add new functionality.
The following code demonstrates the application of the Visitor pattern of class Tmember described above.
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;
{TPProperty}
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;
illustrate:
? TMember, TField, TMethod and Tproperty all implement the AcceptMemberVisitor method. These methods are embedded in the pattern
? The TMemberVisitor class implements VisitMember, VisitField and other methods. TmemberVisitor is an abstract class, and all its methods are implemented by concrete subclasses.
Below is an implementation of a simple code generator.
Code introduction:
• TCodeGenerationVisitor is a visitor for the code generator that implements the member.
? The visitor defines a context-sensitive property: Output: TTextStream,
? It must be set before VisitXXX is called. For example: DrawingVisitor typically requires a context including canvas to support drawing operations. The context is assigned to the code generator before iterating through the entire member pair.
? The code generator will consolidate all the code for the generated class
To really understand the Visitor mode, you can execute this example and further learn the double dispatch mechanism: 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 := ';
{The code is incomplete, it is enough to demonstrate Tmethod code generation}
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
{Write class definition}
Output.WriteLine('TSample = class (TObject)');
{good! Visitors who joined the code generator}
Visitor := TCodeGenerationVisitor.Create;
Try
{Remember to provide context for all visits for better access to VisitXXX methods. }
for I := 0 to Members.Count - 1 do
{The specific section of code where something good happened}
TMember(Members[I]).AcceptMemberVisitor(Visitor);
finally
Visitor.Free;
end;
{Code generation for class members completed}
Output.WriteLine('end;');
end;
Organizing
//Many excerpts from "Design Patterns",