【问题标题】:Delphi Enterprise: how can I apply the Visitor Pattern without circular references?Delphi Enterprise:如何在没有循环引用的情况下应用访问者模式?
【发布时间】:2010-03-01 14:09:35
【问题描述】:

使用 Delphi 2009 Enterprise,我在模型视图中为 GoF 访问者模式创建了代码,并将代码分成两个单元:一个用于域模型类,一个用于访问者(因为我可能需要其他单元来实现不同的访问者,一切都在一个单元中?'Big ball of mud'前面!)。

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 问题,以及硬转换之类的技巧?

【问题讨论】:

标签: delphi oop delphi-2009 visitor-pattern


【解决方案1】:

我使用以下方案来实现灵活的访问者模式:

基本访问者类型声明

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;

【讨论】:

    【解决方案2】:

    为什么不定义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.
    

    这样你就不会混合类和接口引用(总是一个坏主意)

    【讨论】:

    • 在你的VisitElement方法中,参数是IElement类型,所以方法体只能访问IElement中声明的方法和属性,不能访问TConcreteElement实例的属性或方法。访问者实际上为每个访问的类类型声明了一个方法:过程 VisitTConcreteElement1(AElement :TConcreteElement1), 过程 VisitTConcreteElement2(AElement :TConcreteElement2), (...) VisitTConcreteElementN(AElement :TConcreteElementN)
    【解决方案3】:

    以下实现在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;
    

    【讨论】:

    • 编译器错误在线E.Accept(TConcreteVisitor.Create as IVisitor&lt;TElement&gt;);: [DCC 错误] Project1.dpr(16): E2015 运算符不适用于此操作数类型 Juste 将其保留为:E.Accept(TConcreteVisitor.Create);
    • @TridenT:我正在使用XE4XE5XE6 进行编译,在E.Accept(TConcreteVisitor.Create as IVisitor&lt;TElement&gt;) 行没有发现编译器错误
    • 哼……奇怪!我在XE6上测试过,必须删除as IVisitor&lt;TElement&gt;部分才能编译。
    • @TridenT:这听起来很奇怪。我对我的样本进行了双重测试,它可以正常工作。
    【解决方案4】:

    TConcreteElement 的声明应该在 VisitorUnit(或第三个单元)中

    或更好

    IVisitator 应改为:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-15
      • 1970-01-01
      • 2010-11-25
      • 2021-03-31
      • 2019-10-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多