【问题标题】:How to link "parallel" class hierarchy?如何链接“并行”类层次结构?
【发布时间】:2009-02-02 12:46:55
【问题描述】:

我有一个小的类层次结构,其中每个类对应于某个 TComponent 后代(比如基类 TDefaultFrobber 与后代 TActionFrobber 和 TMenuItemFrobber,分别对应于 TComponent、TCustomAction 和 TMenuItem)。现在我想要一个像这样的工厂(?)函数:

function CreateFrobber(AComponent: TComponent): IFrobber;
begin
  if AComponent is TCustomAction then
    Result := TActionFrobber.Create(TCustomAction(AComponent))
  else if AComponent is TMenuItem then
    Result := TMenuItemFrobber.Create(TMenuItem(AComponent))
  else
    Result := TDefaultFrobber.Create(AComponent);
end;

我能否以某种方式重构它以使用虚函数或类似的东西而不是 if-else 级联或 RTTI?

编辑:我现在的解决方案:

unit Frobbers;

interface

uses
  Classes;

type
  IComponentFrobber = interface
  end;

  TComponentFrobberClass = class of TComponentFrobber;

  TComponentFrobber = class(TInterfacedObject, IComponentFrobber)
  strict private
    FComponent: TComponent;
  protected
    constructor Create(AComponent: TComponent);
    property Component: TComponent read FComponent;
  public
    class function FindFrobberClass(AComponentClass: TComponentClass): TComponentFrobberClass; overload; static;
    class function FindFrobberClass(AComponent: TComponent): TComponentFrobberClass; overload; static;
    class procedure RegisterFrobber(AComponentClass: TComponentClass; AFrobberClass: TComponentFrobberClass); static;
  end;

implementation

uses
  ActnList,
  Menus;

type
  TComponentFrobberRegistryItem = record
    ComponentClass: TComponentClass;
    FrobberClass: TComponentFrobberClass;
  end;

var
  FComponentFrobberRegistry: array of TComponentFrobberRegistryItem;

class function TComponentFrobber.FindFrobberClass(AComponentClass: TComponentClass): TComponentFrobberClass;
var
  i: Integer;
begin
  // Search backwards, so that more specialized frobbers are found first:
  for i := High(FComponentFrobberRegistry) downto Low(FComponentFrobberRegistry) do
    if FComponentFrobberRegistry[i].ComponentClass = AComponentClass then
    begin
      Result := FComponentFrobberRegistry[i].FrobberClass;
      Exit;
    end;
  Result := nil;
end;

constructor TComponentFrobber.Create(AComponent: TComponent);
begin
  inherited Create;
  FComponent := AComponent;
end;

class function TComponentFrobber.FindFrobberClass(AComponent: TComponent): TComponentFrobberClass;
var
  i: Integer;
begin
  // Search backwards, so that more specialized frobbers are found first:
  for i := High(FComponentFrobberRegistry) downto Low(FComponentFrobberRegistry) do
    if AComponent is FComponentFrobberRegistry[i].ComponentClass then
    begin
      Result := FComponentFrobberRegistry[i].FrobberClass;
      Exit;
    end;
  Result := nil;
end;

class procedure TComponentFrobber.RegisterFrobber(AComponentClass: TComponentClass;
  AFrobberClass: TComponentFrobberClass);
var
  i: Integer;
begin
  Assert(FindFrobberClass(AComponentClass) = nil, 'Duplicate Frobber class');
  i := Length(FComponentFrobberRegistry);
  SetLength(FComponentFrobberRegistry, Succ(i));
  FComponentFrobberRegistry[i].ComponentClass := AComponentClass;
  FComponentFrobberRegistry[i].FrobberClass := AFrobberClass;
end;

function CreateComponentFrobber(AComponent: TComponent): IComponentFrobber;
var
  FrobberClass: TComponentFrobberClass;
begin
  FrobberClass := TComponentFrobber.FindFrobberClass(AComponent);
  Assert(FrobberClass <> nil);
  Result := FrobberClass.Create(AComponent);
end;

type
  TActionFrobber = class(TComponentFrobber);
  TMenuItemFrobber = class(TComponentFrobber);

initialization
  TComponentFrobber.RegisterFrobber(TCustomAction, TActionFrobber);
  TComponentFrobber.RegisterFrobber(TMenuItem, TMenuItemFrobber);
end.

感谢 Cesar、Gamecat 和 mghie。

【问题讨论】:

  • 添加了一些 cmets 的答案,因为它们永远不适合这里 ;-)

标签: delphi virtual rtti class-hierarchy


【解决方案1】:

如果您使用虚拟构造函数创建一个类并为该类创建一个类类型。您可以根据组件类名称创建查找列表。

例子:

type
  TFrobber = class 
  public
    constructor Create; virtual;

    class function CreateFrobber(const AComponent: TComponent): TFrobber;
  end;
  TFrobberClass = class of TFrobber;

  type 
    TFrobberRec = record 
      ClassName: ShortString;
      ClassType: TFrobberClass;
    end;

  const
    cFrobberCount = 3;
    cFrobberList : array[1..cFrobberCount] of TFrobberRec = (
      (ClassName : 'TAction'; ClassType: TActionFrobber),
      (ClassName : 'TButton'; ClassType: TButtonFrobber),
      (ClassName : 'TMenuItem'; ClassType: TMenuItemFrobber)
    );

  class function TFrobber.CreateFrobber(const AComponent: TComponent): TFrobber;
  var
    i : Integer;
  begin
    Result := nil;
    for i := 1 to cFrobberCount do begin
      if AComponent.ClassName = cFrobberList[i].ClassName then begin
        Result := cFrobberList[i].ClassType.Create();
        Exit;
      end;
    end;
  end;

您当然也可以使用动态列表(字典),但是您必须以某种方式注册每个组合。

更新

评论mghie的言论。

你完全正确。但这不可能没有真正丑陋的把戏。 现在你必须使用一个单元的初始化/终结部分来重新注册一个类。但是将初始化/终结类方法添加到类中会很酷。这些必须与单元的初始化(和终结)一起调用。像这样:

class 
  TFrobber = class
  private
    initialization Init; // Called at program start just after unit initialization
    finalization Exit;  // called at program end just before unit finalization.
  end;

【讨论】:

  • +1 的想法,但肯定会使用动态注册的标准类对和匹配的 Frobber 类。这样,您就可以在所有事物之间获得更松散的耦合。类注册表不需要知道所有具体类,因为答案中的代码是必需的。
  • 完美的设计是在 IMO 中实现的,只需添加一个新单元即可将新的 Frobber 类添加到系统中,而无需更改旧代码。
  • 我真的在寻找一种更“编译时安全”的方法,但我想 RegisterClass 类型是我得到的最接近的方法。也许用 TComponentClass 而不是像 Cesar 提议的类名。
  • 使用组件类进行比较有一个小问题,因为它可能导致任何组件与组件类匹配。这就是为什么我使用类名,只是为了安全。
  • @Gamecat:重新编辑 - 我不认为需要在初始化子句中注册是一个真正的问题。如果不需要,那么当然也可以手动注册 Frobber 类。这也为可用的 Frobber 类的运行时更改打开了大门。类初始化看起来不错!
【解决方案2】:

2 条建议: 制作类的类对数组,然后你可以得到索引并使用类构造函数的对,

var
  ArrayItem: array[0..1] of TComponentClass = (TActionFrobber, TMenuItemFrobber);
  ArrayOwner: array[0..1] of TComponentClass = (TCustomAction, TMenuItem);

function CreateFrobber(AComponent: TComponentClass): IFrobber;
var
  Index: Integer;
begin
  Result:= nil;
  for I := Low(ArrayOwner) to High(ArrayOwner) do
    if AComponent is ArrayOwner[I] then
    begin
      Result:= ArrayItem[I].Create(AComponent);
      Break;
    end;

  if Result = nil then
    Result:= TDefaultFrobber.Create(AComponent);
end;

或使用 RTTI + ClassName 约定,如下所示:

function CreateFrobber(AComponent: TComponentClass): IFrobber;
const 
  FrobberClassSuffix = 'Frobber';
var
  LClass: TComponentClass;
  LComponent: TComponent;
begin
  LClass:= Classes.FindClass(AComponent.ClassName + FrobberClassSuffix);
  if LClass <> nil then 
    LComponent:= LClass.Create(AComponent) 
  else
    LComponent:= TDefaultFrobber.Create(AComponent);

  if not Supports(LComponent, IFrobber, Result) then
    Result:= nil;
end;

【讨论】:

  • RTTI + ClassName 约定的想法太脆弱了IMO,为什么要把自己画到这样的角落?
  • 我同意,并且更喜欢类对数组。我只是提出了另一种选择。用不用,由他自己决定,至少他知道这样可以做到。
【解决方案3】:

我想在您当前的解决方案中添加一些 cmets,在此回答,因为这在 cmets 部分实际上无法完成:

type
  IComponentFrobber = interface
  end;

  TComponentFrobberClass = class of TComponentFrobber;

  TComponentFrobber = class(TInterfacedObject, IComponentFrobber)
  strict private
    FComponent: TComponent;
  protected
    constructor Create(AComponent: TComponent);
    property Component: TComponent read FComponent;
  public
    class function FindFrobberClass(AComponentClass: TComponentClass):
      TComponentFrobberClass; overload; static;
    class function FindFrobberClass(AComponent: TComponent):
      TComponentFrobberClass; overload; static;
    class procedure RegisterFrobber(AComponentClass: TComponentClass;
      AFrobberClass: TComponentFrobberClass); static;
  end;

将 TInterfacedObject 用于基类没有多大意义,因为您将始终需要对象,而不是它实现的接口——否则您将如何找到具体的 Frobber 类?我会将它拆分为 TComponentFrobber,从 TInterfacedObject 下降,以及具有类方法的 TComponentRegistry 类(从 TObject 下降)。然后,您当然可以使注册表类更通用,它不绑定到 TComponentFrobber 并且可以重用。

编辑:我在加载文件时使用了类似的类注册表:加载下一个对象的标识符(可以是字符串、整数或 GUID),然后获取正确的类来实例化从注册表中,然后创建并加载对象。

type
  TComponentFrobberRegistryItem = record
    ComponentClass: TComponentClass;
    FrobberClass: TComponentFrobberClass;
  end;

var
  FComponentFrobberRegistry: array of TComponentFrobberRegistryItem;

如果您永远不会在注册表中添加或删除类,这是可以的,但通常我不会使用数组而是注册表条目的列表。

class function TComponentFrobber.FindFrobberClass(AComponentClass: TComponentClass):
  TComponentFrobberClass;
var
  i: Integer;
begin
  // Search backwards, so that more specialized frobbers are found first:
  for i := High(FComponentFrobberRegistry) downto Low(FComponentFrobberRegistry) do
    if FComponentFrobberRegistry[i].ComponentClass = AComponentClass then
    begin
      Result := FComponentFrobberRegistry[i].FrobberClass;
      Exit;
    end;
  Result := nil;
end;

在数组中向后搜索将无助于找到最专业的 frobber,除非您以正确的顺序添加它们(最不专业的在前)。为什么不检查 ClassType 是否相等?如果您也需要测试基类,还有 ClassParent 可以遍历类层次结构。

【讨论】:

  • IComponentFrobber 接口纯粹用于生命周期管理 - 使用 frobbers 时无需尝试/最终。我想我可以改用一些 IObjectSafe 实现。
  • 重新数组:数组只是使用更简单 - 不需要释放它,不需要派生容器类,可以保存指针对等。从注册表中删除不会发生 -现在只在初始化部分 AFAICT 中调用 RegisterFrobber。
  • 好的。不是你应该改变你的代码,也许它会在一段时间内帮助别人,StackOverflow毕竟是要变成wiki的。
  • 不这么理解。我只是感谢你的 cmets。
猜你喜欢
  • 1970-01-01
  • 2011-08-10
  • 1970-01-01
  • 2011-01-21
  • 1970-01-01
  • 2011-10-17
  • 1970-01-01
  • 2019-06-05
  • 2015-05-14
相关资源
最近更新 更多