4

With Delphi 2009 Enterprise I created code for the GoF Visitor Pattern in the model view, and separated the code in two units: one for the domain model classes, one for the visitor (because I might need other units for different visitor implementations, everything in one unit? 'Big ball of mud' ahead!).

unit VisitorUnit;

interface

uses
  ConcreteElementUnit;

type
  IVisitor = interface;

  IElement = interface
  procedure Accept(AVisitor :IVisitor);
  end;

  IVisitor = interface
  procedure VisitTConcreteElement(AElement :TConcreteElement);
  end;

  TConcreteVisitor = class(TInterfacedObject, IVisitor)
  public
    procedure VisitTConcreteElement(AElement :TConcreteElement);
  end;

implementation

procedure TConcreteVisitor.VisitTConcreteElement(AElement :TConcreteElement);
begin
  { provide implementation here }
end;

end.

and the second unit for the business model classes

unit ConcreteElementUnit;

interface

uses
  VisitorUnit;

type
  TConcreteElement = class(TInterfacedObject, IElement)
  public
    procedure Accept(AVisitor :IVisitor); virtual;
  end;

  Class1 = class(TConcreteElement)
  public
    procedure Accept(AVisitor :IVisitor);
  end;

implementation

{ Class1 }

procedure Class1.Accept(AVisitor: IVisitor);
begin
  AVisitor.VisitTConcreteElement(Self);
end;

end.

See the problem? A circular unit reference. Is there an elegant solution? I guess it requires "n+1" additional units with base interface / base class definitions to avoid the CR problem, and tricks like hard casts?

mjn
  • 36,362
  • 28
  • 176
  • 378
  • 2
    I am still waiting for a comment like 'circular references are an indicator of bad design' :) – mjn Mar 02 '10 at 13:09
  • See also: http://www.uweraabe.de/Blog/2010/08/16/the-visitor-pattern-part-2/ – mjn Nov 18 '14 at 17:33

4 Answers4

4

I use the following scheme to implement a flexible visitor pattern:

Declaration of base visitor types

unit uVisitorTypes;
type
  IVisited = interface
  { GUID }
    procedure Accept(Visitor: IInterface);
  end;

  IVisitor = interface
  { GUID }
    procedure Visit(Instance: IInterface);
  end;

  TVisitor = class(..., IVisitor)
    procedure Visit(Instance: IInterface);
  end;

procedure TVisitor.Visit(Instance: IInterface);
var
  visited: IVisited;
begin
  if Supports(Instance, IVisited, visited) then 
    visited.Accept(Self)
  else
    // raise exception or handle error elsewise    
end;

The unit for of the element class

unit uElement;

type
  TElement = class(..., IVisited)
    procedure Accept(Visitor: IInterface);
  end;

  // declare the visitor interface next to the class-to-be-visited declaration   
  IElementVisitor = interface
  { GUID }
    procedure VisitElement(Instance: TElement);
  end;

procedure TElement.Accept(Visitor: IInterface);
var
  elementVisitor: IElementVisitor;
begin
  if Supports(Visitor, IElementVisitor, elementVisitor) then
    elementVisitor.VisitElement(Self)
  else
    // if override call inherited, handle error or simply ignore
end;

The actual visitor implementation

unit MyVisitorImpl;

uses
  uVisitorTypes, uElement;

type
  TMyVisitor = class(TVisitor, IElementVisitor)
    procedure VisitElement(Instance: TElement);
  end;

procedure TMyVisitor.VisitElement(Instance: TElement);
begin
  // Do whatever you want with Instance 
end;

Calling the visitor

uses
  uElement, uMyElementVisitor;

var
  visitor: TMyVisitor;
  element: TElement;
begin
  // get hands on some element

  visitor := TMyVisitor.Create;
  try
    visitor.Visit(element);
  finally
    visitor.Free;
  end;
end;
Uwe Raabe
  • 45,288
  • 3
  • 82
  • 130
1

Why not define IVisitor

IVisitor = interface
  procedure VisitElement(AElement :IElement);
end; 

then TConcreteElement in its own unit :

unit ConcreteElementUnit;

interface

uses
  VisitorUnit;

type
  TConcreteElement = class(TInterfacedObject, IElement)
  public
    procedure Accept(AVisitor :IVisitor); virtual;
  end;

  Class1 = class(TConcreteElement)
  public
    procedure Accept(AVisitor :IVisitor);
  end;

implementation

{ Class1 }

procedure Class1.Accept(AVisitor: IVisitor);
begin
  AVisitor.VisitElement(Self);
end;

end.

That way you are not mixing class and interface references (always a bad idea)

Steve
  • 6,382
  • 3
  • 41
  • 66
  • In your VisitElement method, the argument is of type IElement, so the method body can only access methods and properties declared in IElement, but no properties or methods of TConcreteElement instances. A Visitor actually declares a method for every visited class type: procedure VisitTConcreteElement1(AElement :TConcreteElement1), procedure VisitTConcreteElement2(AElement :TConcreteElement2), (...) VisitTConcreteElementN(AElement :TConcreteElementN) – mjn Mar 01 '10 at 18:30
1

The following implementation using generic type on Visitor interface to solve the circular reference issue of Visitor pattern:

Visitor.Intf.pas:

unit Visitor.Intf;

interface

type
  IVisitor<T> = interface
    procedure Visit_Element(o: T);
  end;

implementation

end.

Element.pas:

unit Element;

interface

uses Visitor.Intf;

type
  TElement = class
    procedure Accept(const V: IVisitor<TElement>);
  end;

implementation

procedure TElement.Accept(const V: IVisitor<TElement>);
begin
  V.Visit_Element(Self);
end;

end.

Visitor.Concrete.pas:

unit Visitor.Concrete;

interface

uses Element, Visitor.Intf;

type
  TConcreteVisitor = class(TInterfacedObject, IVisitor<TElement>)
  protected
    procedure Visit_Element(o: TElement);
  end;

implementation

procedure TConcreteVisitor.Visit_Element(o: TElement);
begin
  // write implementation here
end;

end.

Using the TElement and TConcreteVisitor class:

var E: TElement;
begin
  E := TElement.Create;
  E.Accept(TConcreteVisitor.Create as IVisitor<TElement>);
  E.Free;
end;
Chau Chee Yang
  • 18,422
  • 16
  • 68
  • 132
  • Compiler error on line `E.Accept(TConcreteVisitor.Create as IVisitor);`: [DCC Error] Project1.dpr(16): E2015 Operator not applicable to this operand type Juste leav it as : `E.Accept(TConcreteVisitor.Create);` – TridenT Aug 26 '14 at 11:52
  • @TridenT: I am using `XE4`, `XE5` and `XE6` to compile and no compiler error found at line `E.Accept(TConcreteVisitor.Create as IVisitor)` – Chau Chee Yang Aug 27 '14 at 00:32
  • hum ... strange! I tested on XE6 and must delete the `as IVisitor` part to compile. – TridenT Aug 27 '14 at 07:37
  • @TridenT: That sounds strange. I have double test my sample and it works without errors. – Chau Chee Yang Aug 27 '14 at 13:29
0

The decleration of TConcreteElement shoud be in VisitorUnit (or a third unit)

or better

The IVisitator should be changed to:

 IVisitor = interface
  procedure VisitTConcreteElement(AElement :IElement);
 end;
BennyBechDk
  • 934
  • 7
  • 13