使用实现两个接口的对象的最佳实践是什么?

时间:2018-02-27 11:56:36

标签: delphi

这可能是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.

那么您首选哪种方式?还是其他任何建议?提前谢谢。

1 个答案:

答案 0 :(得分:9)

这里存在很多混乱和复杂性。我会告诉你我将如何做,而不是试图剖析你所拥有的东西。

首先删除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运算符获取其他接口。