【问题标题】:Interface delegation + overriding接口委托+覆盖
【发布时间】:2014-08-18 17:25:25
【问题描述】:

由于 Delphi 中缺少多重继承,我需要使用接口委托。这对我来说是一个非常新的话题,我在将覆盖与接口委托结合使用时遇到了问题。

TMyNode 类必须继承自TBaseClass,并且需要实现IAddedStuff。我想在TAddedStuffDefaultImplementation 中拥有IAddedStuff 的所有功能的默认实现,所以我不需要到处都有getter/setter 的重复代码。所以,我已经使用DefaultBehavior 委派了这些事情。

问题是,TAddedStuffDefaultImplementation 意味着有虚拟方法,所以我想直接在 TMyNode 中覆盖它们。如果我写 FDefaultImplementation: TAddedStuffDefaultImplementation; 而不是 FDefaultImplementation: IAddedStuff; ,这确实有效。

但是现在,由于某些原因TAddedStuffDefaultImplementation 会增加x: TBaseClass; 的 Ref-Counter,所以它不能被释放。我该怎么办?

我的简化复制代码如下:

program Project2;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
  IAddedStuff = interface(IInterface)
  ['{9D5B00D0-E317-41A7-8CC7-3934DF785A39}']
    function GetCaption: string; {virtual;}
  end;

  TAddedStuffDefaultImplementation = class(TInterfacedObject, IAddedStuff)
    function GetCaption: string; virtual;
  end;

  TBaseClass = class(TInterfacedObject);

  TMyNode = class(TBaseClass, IAddedStuff)
  private
    FDefaultImplementation: TAddedStuffDefaultImplementation;
  public
    property DefaultBehavior: TAddedStuffDefaultImplementation read FDefaultImplementation
      write FDefaultImplementation implements IAddedStuff;
    destructor Destroy; override;

    // -- IAddedStuff
    // Here are some functions which I want to "override" in TMyNode.
    // All functions not declared here, should be taken from FDefaultImplementation .
    function GetCaption: string; {override;}
  end;

{ TAddedStuffDefaultImplementation }

function TAddedStuffDefaultImplementation.GetCaption: string;
begin
  result := 'PROBLEM: CAPTION NOT OVERRIDDEN';
end;

{ TMyNode }

destructor TMyNode.Destroy;
begin
  if Assigned(FDefaultImplementation) then
  begin
    FDefaultImplementation.Free;
    FDefaultImplementation := nil;
  end;

  inherited;
end;

function TMyNode.GetCaption: string;
begin
  Result := 'OK: Caption overridden';
end;

var
  x: TBaseClass;
  gn: IAddedStuff;
  s: string;
begin
  x := TMyNode.Create;
  try
    TMyNode(x).DefaultBehavior := TAddedStuffDefaultImplementation.Create;
    Assert(Supports(x, IAddedStuff, gn));
    WriteLn(gn.GetCaption);
  finally
    WriteLn('RefCount = ', x.RefCount);
    // x.Free; // <-- FREE fails since FRefCount is 1
  end;
  ReadLn(s);
end.

【问题讨论】:

  • 我不熟悉接口,但我有一个问题。 TMyNode 继承 TBaseClassIAddedStuffTBaseClass 继承 TInterfacedObjectTAddedStuffDef.... 也继承 TInterfacedObjectIAddedStuff 所以基本上如果 TMyNode 继承 TAddedStuffDef.... 不应该具有相同的功能集吗?对我来说似乎是双重的,但也许我错了:)
  • TMyNode 需要从 TAddedStuffDefaultImplementation AND TBaseClass 继承。由于多重继承是不可能的,我需要做上面的所有事情。 :-( 在我的简化示例中,TBaseClass 没有其他内容,因为它与问题无关。
  • 文档使用接口,而您的代码使用类引用——混合使用是引用计数问题的常见来源。
  • TInterfacedObject 的锚是引用计数的(除非你改变它)并且永远不应该被释放。
  • 好的,让我澄清一下。 TInterfacedObject 的 Achestor 使用引用计数实现接口,并且这些对象实例的生命周期由这些接口的引用计数控制。你不应该在这些对象上使用 Free。这与不实现引用计数的 TInterfacedPersistent 不同。

标签: delphi oop interface delegation


【解决方案1】:

如果您委托IAddedStuff,那么您还应该在另一个类上实现非默认行为并通过构造函数注入传递它。

此外,如果您正在混合对象和接口引用,请确保引用计数不冲突。使用接口委托时,容器对象的引用会发生变化。

program Project1;

{$APPTYPE CONSOLE}

uses
  Classes,
  SysUtils;

type
  IAddedStuff = interface(IInterface)
  ['{9D5B00D0-E317-41A7-8CC7-3934DF785A39}']
    function GetCaption: string; {virtual;}
  end;

  TAddedStuffDefaultImplementation = class(TInterfacedObject, IAddedStuff)
    function GetCaption: string; virtual;
  end;

  TAddedStuffOverriddenImplementation = class(TAddedStuffDefaultImplementation)
    function GetCaption: string; override;
  end;

  TBaseClass = class(TInterfacedPersistent);

  TMyNode = class(TBaseClass, IAddedStuff)
  private
    FAddedStuff: IAddedStuff;
    property AddedStuff: IAddedStuff read FAddedStuff implements IAddedStuff;
  public
    constructor Create(const addedStuff: IAddedStuff);
  end;

{ TAddedStuffDefaultImplementation }

function TAddedStuffDefaultImplementation.GetCaption: string;
begin
  result := 'PROBLEM: CAPTION NOT OVERRIDDEN';
end;

{ TAddedStuffOverriddenImplementation }

function TAddedStuffOverriddenImplementation.GetCaption: string;
begin
  Result := 'OK: Caption overridden';
end;

{ TMyNode }

constructor TMyNode.Create;
begin
  FAddedStuff := addedStuff;
end;

var
  x: TBaseClass;
  gn: IAddedStuff;
begin
  x := TMyNode.Create(TAddedStuffOverriddenImplementation.Create);
  try
    Assert(Supports(x, IAddedStuff, gn));
    WriteLn(gn.GetCaption);
  finally
    x.Free;
  end;
  Readln;
  ReportMemoryLeaksOnShutdown := True;
end.

编辑:

在 cmets 讨论后,我建议如下:

program Project1;

{$APPTYPE CONSOLE}

uses
  Classes,
  SysUtils;

type
  IAddedStuff = interface(IInterface)
  ['{9D5B00D0-E317-41A7-8CC7-3934DF785A39}']
    function GetCaption: string;
  end;

  TAddedStuffDefaultImplementation = class(TInterfacedObject, IAddedStuff)
    function GetCaption: string; virtual;
  end;

  TBaseClass = class(TInterfacedPersistent);

  TMyNode = class(TBaseClass, IAddedStuff)
  private
    FAddedStuff: IAddedStuff;
    property AddedStuff: IAddedStuff read FAddedStuff implements IAddedStuff;
  public
    constructor Create;
  end;

  TAddedStuffOverriddenImplementation = class(TAddedStuffDefaultImplementation)
  private
    FMyNode: TMyNode;
  public
    constructor Create(AMyNode: TMyNode);
    function GetCaption: string; override;
  end;

{ TAddedStuffDefaultImplementation }

function TAddedStuffDefaultImplementation.GetCaption: string;
begin
  result := 'PROBLEM: CAPTION NOT OVERRIDDEN';
end;

{ TMyNode }

constructor TMyNode.Create;
begin
  FAddedStuff := TAddedStuffOverriddenImplementation.Create(Self);
end;

{ TAddedStuffOverriddenImplementation }

constructor TAddedStuffOverriddenImplementation.Create(AMyNode: TMyNode);
begin
  FMyNode := AMyNode;
end;

function TAddedStuffOverriddenImplementation.GetCaption: string;
begin
  Result := 'OK: Caption overridden';
end;


var
  x: TBaseClass;
  gn: IAddedStuff;
begin
  x := TMyNode.Create;
  try
    Assert(Supports(x, IAddedStuff, gn));
    WriteLn(gn.GetCaption);
  finally
    x.Free;
  end;
  ReadLn;
  ReportMemoryLeaksOnShutdown := True;
end.

【讨论】:

  • 非常感谢您的快速回复。我确实混合了对象和接口引用,因为这是使“覆盖”工作的唯一可能性。由于某些原因,如果私有字段是对象引用,则会调用被覆盖的变体,但如果是接口引用则不会。
  • 您的代码解决了引用计数的问题,但唉 TAddedStuffOverriddenImplementation 无法访问 TMyNode 。这背后的想法是TAddedStuffDefaultImplementation.GetCaptions 只输出“未知”(一些常量),而TMyNode.GetCaption 输出一些特定的数据,如“用户:托马斯”。是否有任何其他选项我必须在TMyNode 中添加GetCaption,但仍然覆盖接口的默认实现(主要是 getter 和 setter 以及“未知”标题+图标)?
  • 您是否创建了TAddedStuffDefaultImplementation 以在不同的其他类中重用它,而无需每次都实现IAddedStuff?否则,您可以将默认实现方法很好地放在TBaseClassWithAddedStuff 中,将它们设为虚拟,然后在您的TMyNode 中覆盖它们。
  • 是的,TAddedStuffDefaultImplementation 的目的是与所有类共享它,这样如果它们不支持特定的东西,它们就不需要实现 20 个虚拟方法。唉,我无法修改TBaseClass,因为我需要将 API 和特定于供应商的插件分成 2 个单元。这里的这个问题是我试图解决这个问题时发生的后续问题:stackoverflow.com/a/24439228/3544341.
  • this image 中,您可以看到(示例)TGraphicImageNodeTContactImageNode 都实现了IGraphicNode。在实际项目中,会有更多的类实现这个接口,但问题是,90% 的代码只是默认代码,不会被覆盖,应该是默认实现。我担心我已经达到了 Delphi 中 OOP 的极限。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-28
  • 2021-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-20
  • 2016-09-04
相关资源
最近更新 更多