接口

接口

接口声明

声明一个接口的语法如下:

1
2
3
4
5
type
接口名称 Interface(父接口的名称)
[GUID]
//成员列表
end;
  • 没有明确指定的父接口时默认继承自 IInterface 接口
  • 接口声明中的 GUID 可省略不写,但是建议不要省略
  • IDE 中只要同时按下 Ctrl+Shift+G 即可自动产生一个完整的 GUID
  • 接口的成员不能是字段,只能是方法与属性
  • 在声明属性时也不允许指定存储限定符( storeddefaultnodefault)。但数组属性可指定 default
  • 由接口不能实例化,其中的方法不存在动态或静态绑定,故而方法后不能加关键词:virtual, dynamic, abstract,以及 override

接口的前置声明

接口也可以前置声明,允许接口的真实声明延后

实现接口

接口里面的方法和属性,全部指示声明;具体实现需要放到类中,语法如下:

1
2
3
4
type
子类名称 = class (父类名称, 接口1, 接口2, ... , 接口n)
// 类的成员列表
end;

实现接口前的父类类型名不能省略,可以写默认的 TObject

方法别名

如果两个接口拥有相同的方法或属性,而一个类同时实现了这两个接口,则同名问题会导致编译出问题,可以采用方法别名的办法来处理:

1
2
procedure 接口.过程 = 方法别名;
function 接口.函数 = 方法别名;

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
type
I1 = Interface
procedure SetX(value:integer);
function GetX:integer;
property P1:integer read GetX write SetX;
end;
I2 = Interface
procedure SetX(value:integer);
function GetX:integer;
property P2:integer read GetX write SetX;
end;

T1 = class(TInterfacedObject, I1, I2)
strict private
i, j: integer;
procedure I1.SetX = set1; //替 I1 中的 SetX 指定别名为 set1,以下同理
function I1.GetX = get1;
procedure I2.SetX = set2;
function I2.GetX = get2;
public
procedure Set1(value: integer); //定义 Set1
function Get1: integer;
procedure Set2(value: integer);
function Get2:integer;
property p1: integer read Get1 write Set1; //完全使用 set1 代替 SetX
property p2: integer read Get2 write Set2;
end;

接口代理

一个典型场景:类A实现了接口1,类2也实现了接口1;两者的实现代码是一样的。然后后期实现方法有改动,类A中的实现代码修改之后,类A中的实现代码同样也要改一遍。这样的方式很不复合软件代码复用的原则。

接口代理可以避免这种重复。

首先实现一个代理类,这个代理类实现了接口。然后正常类需要复用这个代理类的接口实现时,声明一个代理类的属性,利用属性的 Implements 指示字,自动代理实现接口的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
type
// 声明接口 IMyInterface
IMyInterface = Interface
procedure func1;
end;

// 实现 IMyInterface 接口的代理类
TDelegateClass = class(TObject, IMyInterface)
procedure func1;
end;

// 普通类,也要实现接口 IMyInterface
TMyCommon = class(TObject, IMyInterface) // 必须写明实现了 IMyInterface
strict private
FInterface: IMyInterface;
public
property P1: IMyInterface
read FInterface
write FInterface
Implements IMyInterface; // 通过代理类的属性实现了 IMyInterface
end;

procedure TDelegateClass.func1;
begin
writeln('this is TDelegateClass.func1');
end;

var
interface1: IMyInterface;
obj: TMyCommon;
begin
obj := TMyCommon.Create;
obj.P1 := TDelegateClass.Create; // 代理属性也需要赋值
interface1 := obj;
interface1.func1; // 这时可以通过接口调用方法的实现
readln;
end.

多个接口的代理实现的写法(属性的 implements 指示字后写明实现多个接口):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type  
IMyInterface = interface
procedure P1;
end;
IMyInterface1 = interface
procedure P2;
end;

// 代理类写明实现了 接口1 和 接口2
TDelegateClass = class(TObject, IMyInterface, IMyInterface1)
private
procedure P1;
procedure P2;
end;

// 普通类写明实现了 接口1 和 接口2
TMyClass = class(TInterfacedObject, IMyInterface, IMyInterface1)
private
FMyInterface: TDelegateClass; // 做一个代理类的属性
property MyInterface: TDelegateClass
read FMyInterface implements IMyInterface, IMyInterface1;
end;

接口的赋值与转换

对象与接口之间赋值

看下面例子,实现接口的类对象,可以直接赋值给接口对象;子接口可以赋值给基接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type
I1 = Interface(IInterface)
// 省略成员
End;
I2 = Interface(I1)
// 省略成员
End;

T1 = class(TInterfacedObject, I2)
// 省略成员
end;
var
obj: T1;
interface1: I1;
interface2: I2;
begin
obj := T1.Create;
interface1 := obj;
//上面此句错误: obj 实现了 I2 接口,它不能作为值赋给除 I2 以外的任何接口类型的变量
interface2 := obj; // OK
interface1 := interface2; // OK,由派生接口赋值给基接口是 OK 的
FreeAndNil(obj);
end.

接口的类型转换

上述的例子中,可以将对象赋值给接口对象,这是一种最常见的转换。

如果知道接口对象,也可以转换为类对象,但是需要强制类型转换的操作,如下:

1
obj := T1(interface2);   // 接口强制转换回类对象

使用 as 运算符也可以将对象引用转换为接口的引用:

1
interface1 := obj as I1;

当然,利用 as 操作符也可把接口转回对象引用:

1
2
3
4
5
6
7
8
9
10
11
begin
obj := T1.Create;
interface1 := obj as I1;
//加入 3 行代码
obj := nil;
obj := interface1 as T1;
obj.M3();
//至此
interface1.M1;
Readln;
end

也可以使用 is 运算符来测试接口的引用是否可以兼容类,例如:

1
2
3
4
if interface1 is T1 then
begin
obj1 := T1(interface1); // 这时的强制转换是安全的
end;

对象名实际上代表了对象实体的一个引用。接口名也代表了一种接口类型的引用。

接口的引用跟普通对象引用不同,在堆中并没有与接口对应的对象实体。接口可以看成是一个只有引用没有实体的对象。接口的引用,实际是指向实现接口的类的普通对象。

接口与引用计数

接口是属于托管的类型,接口会被运行时自动管理,类对象被接口类型引用时,会进行引用计算的记录。