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