Delphi Enterprise: как я могу применить Шаблон посетителя без циклических ссылок? - PullRequest
4 голосов
/ 01 марта 2010

В Delphi 2009 Enterprise я создал код для шаблона посетителя GoF в представлении модели и разделил код на две единицы: одну для классов модели домена, одну для посетителя (поскольку мне могут потребоваться другие единицы для различных реализаций посетителя , все в одной единице? ' Большой шарик грязи ' впереди!).

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.

и второй блок для классов бизнес-модели

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.

Видишь проблему? Круговая единица ссылки. Есть ли элегантное решение? Я полагаю, что это требует "n + 1" дополнительных модулей с определениями базового интерфейса / базового класса, чтобы избежать проблемы CR, и таких хитростей, как жесткое приведение?

Ответы [ 4 ]

4 голосов
/ 01 марта 2010

Я использую следующую схему для реализации гибкого шаблона посетителей:

Декларация базовых типов посетителей

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;

Единица для элемента класса

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;

Реальная реализация посетителя

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;

Звонок посетителю

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;
1 голос
/ 26 августа 2014

Следующая реализация, использующая универсальный тип в интерфейсе Visitor для решения проблемы циклической ссылки на шаблон Visitor:

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.

Использование классов TElement и TConcreteVisitor:

var E: TElement;
begin
  E := TElement.Create;
  E.Accept(TConcreteVisitor.Create as IVisitor<TElement>);
  E.Free;
end;
1 голос
/ 01 марта 2010

Почему бы не определить IVisitor

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

затем TConcreteElement в своем собственном блоке:

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.

Таким образом, вы не смешиваете ссылки на классы и интерфейсы (всегда плохая идея)

0 голосов
/ 01 марта 2010

Decleration TConcreteElement должно быть в VisitorUnit (или третьем модуле)

или лучше

Значение IVisitator следует изменить на:

 IVisitor = interface
  procedure VisitTConcreteElement(AElement :IElement);
 end;
...