【问题标题】:What's the best practice for using an object implementing two interfaces?使用实现两个接口的对象的最佳实践是什么?
【发布时间】:2018-02-27 11:56:36
【问题描述】:

这可能是my previous question 的扩展。

我已经了解到基于接口的变量不能定义为它的原始类型,否则引用计数无法正常自动释放。

但是如果一个类实现了两个接口,那么在创建它的实例时应该定义什么类型呢?

考虑以下代码:

program Project2;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  SysUtils, Classes;

type
  ITestInterface = interface(IInvokable)
    ['{A7BDD122-7DC6-4F23-93A2-B686571AB2C8}']
    procedure TestMethod;
  end;

  IAnotherInterface = interface(IInvokable)
    ['{15FEC4A7-E361-41D0-9D52-170AFAD1794B}']
    procedure AnotherMethod;
  end;

  TTestObj = class(TInterfacedObject, ITestInterface, IAnotherInterface)
    constructor Create;
    destructor Destroy; override;
  private
    FData: TStrings;
  public
    procedure TestMethod;
    procedure AnotherMethod;
  end;

{ TTestObj }

constructor TTestObj.Create;
begin
  FData := TStringList.Create;
end;

destructor TTestObj.Destroy;
begin
  Writeln('Destroy');
  FData.Free;

  inherited;
end;

procedure TTestObj.TestMethod;
begin
  FData.Text := 'TestMethod';
  Writeln(FData.Strings[0]);
end;

procedure TTestObj.AnotherMethod;
begin
  FData.Text := 'AnotherMethod';
  Writeln(FData.Strings[0]);
end;

{ Main }

function CreateObj: TTestObj;
begin
  Result := TTestObj.Create;
end;

function CreateObj_i1: ITestInterface;
begin
  Result := TTestObj.Create;
end;

function CreateObj_i2: IAnotherInterface;
begin
  Result := TTestObj.Create;
end;

procedure Main;
var
  TestObj: ITestInterface;  // It must be declared as an interface type, or it won't be freed correctly.
  AnotherObj: IAnotherInterface;
  NaturalObj: TTestObj;
begin
  { 1st way: The syntax is a bit natural, but easily lead to memory leaks. }
  CreateObj;                // memory leak !
  TestObj := CreateObj;
  TestObj.TestMethod;
  AnotherObj := CreateObj;
  AnotherObj.AnotherMethod;

  TestObj := nil;
  AnotherObj := nil;
  Writeln('----------');

  { 2nd way: The syntax is a bit messy, you should do type conversion carefully. }
  CreateObj_i1;             // object freed correctly.
  TestObj := TTestObj(CreateObj_i2);    // Using ITestInterface(CreateObj_i2) is wrong.
  TestObj.TestMethod;
  AnotherObj := TTestObj(CreateObj_i1); // Using IAnotherInterface(CreateObj_i1) is wrong.
  AnotherObj.AnotherMethod;

  TestObj := nil;           // useless, it won't be be freed until the procedure returns.
  AnotherObj := nil;        // as above.
  Writeln('----------');

  { 3rd way: The syntax is a bit natural, but it's easily lead to access violation if pass the `NaturalObj` out of the procedure. }
  NaturalObj := TTestObj(CreateObj_i1); // Using TTestObj(CreateObj_i2) is okay too.
  NaturalObj.TestMethod;
  NaturalObj.AnotherMethod;
end;

begin
  Writeln('Program start!');
  Main;
  Writeln('Program end.');
  Readln;
end.

那么您更喜欢哪种方式?或者有什么其他建议?提前致谢。

【问题讨论】:

  • 在“第二种方式”中,在调用CreateObj_i1CreateObj_i2 时去掉TTestObj 类型转换,并交换两个调用:TestObj := CreateObj_i1; TestObj.TestMethod; AnotherObj := CreateObj_i2; AnotherObj.AnotherMethod;
  • 接口就是接口。它不是一个对象。它可以由一个对象实现,通常是这样,但不是必须这样。它也可以使用普通函数来实现。 不要将接口视为对象,将其视为接口并将其用作接口。不要混合这两种。它们是两个不同的东西,尽管对象可以实现接口。哦,不要像你一样使用类型转换。使用 as 在对象和接口之间或不同接口之间进行转换。
  • 因此,如果一个类实现了许多接口,那么它永远不应该定义该类类型的变量(甚至不应该为了方便调用它的方法而使用强制转换),但始终记住该对象由很多部分?有时我觉得应该将对象视为一个整体......@Rudy Velthuis
  • @DDGG,这是不正确的。您仍然可以声明和实例化实现类。但在这种情况下,您必须控制其生命周期(Free 对象)。
  • 您也可以将类用作对象。但不要保留一个接口引用 ir 引用 并且同时 一个对象引用到同一个实例。要么使用那个实例作为接口,要么作为对象,但不能同时作为两者(好吧,除非你真的,真的知道你在做什么)。所以一般来说:不要为同一个对象混合对象和接口引用(但你可以对同一个类的不同实例有不同的引用)。

标签: delphi


【解决方案1】:

这里有很多混乱和复杂性。我不会试图剖析你所拥有的,而是向你展示我将如何做到这一点。

首先删除所有TTestObj 类型的变量。您应该只使用接口引用。您需要为每个变量设置一个变量。

var
  TestIntf: ITestInterface; 
  AnotherIntf: IAnotherInterface;

请注意,我已经更改了这些变量的名称,将Obj 后缀替换为Intf。这反映了它们是接口引用而不是对象引用。

那么你可以简单地这样做:

TestIntf := TTestObj.Create;
AnotherIntf := TestIntf as IAnotherInterface;

现在您有两个接口变量,一个用于您的每个接口。碰巧这两个引用背后的实现对象是同一个对象,这可能是您想要的。

你同样可以颠倒逻辑:

AnotherIntf := TTestObj.Create;
TestIntf := AnotherIntf as ITestInterface;

这达到了完全相同的效果,你可以做到这一点。

如果你想在变量后面有一个不同的实例,那很容易:

TestIntf := TTestObj.Create;
AnotherIntf := TTestObj.Create;

这里的重点是:

  1. 不要混合接口和对象。一旦你开始使用一个接口,就不要访问它背后的实现对象。
  2. 当一个对象实现了多个接口时,使用as操作符来获取其他接口。

【讨论】:

  • 并且:为了使 as 操作符起作用,您将 转换为 的接口必须有一个 GUID。
  • 非常感谢您提供如此详细的说明! @大卫赫弗南
猜你喜欢
  • 1970-01-01
  • 2016-07-17
  • 1970-01-01
  • 2017-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-08
  • 2021-11-10
相关资源
最近更新 更多