接口
接口
接口声明
声明一个接口的语法如下:
1 2 3 4 5
| type 接口名称 = Interface(父接口的名称) [GUID] //成员列表 end;
|
- 没有明确指定的父接口时默认继承自
IInterface
接口
- 接口声明中的
GUID
可省略不写,但是建议不要省略
- IDE 中只要同时按下
Ctrl+Shift+G
即可自动产生一个完整的 GUID
- 接口的成员不能是字段,只能是方法与属性
- 在声明属性时也不允许指定存储限定符(
stored
、 default
、 nodefault
)。但数组属性可指定 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; function I1.GetX = get1; procedure I2.SetX = set2; function I2.GetX = get2; public procedure Set1(value: integer); function Get1: integer; procedure Set2(value: integer); function Get2:integer; property p1: integer read Get1 write Set1; 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 = Interface procedure func1; end;
TDelegateClass = class(TObject, IMyInterface) procedure func1; end;
TMyCommon = class(TObject, IMyInterface) strict private FInterface: IMyInterface; public property P1: IMyInterface read FInterface write FInterface Implements 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; TDelegateClass = class(TObject, IMyInterface, IMyInterface1) private procedure P1; procedure P2; end; 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; interface2 := obj; interface1 := interface2; FreeAndNil(obj); end.
|
接口的类型转换
上述的例子中,可以将对象赋值给接口对象,这是一种最常见的转换。
如果知道接口对象,也可以转换为类对象,但是需要强制类型转换的操作,如下:
使用 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; 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;
|
对象名实际上代表了对象实体的一个引用。接口名也代表了一种接口类型的引用。
接口的引用跟普通对象引用不同,在堆中并没有与接口对应的对象实体。接口可以看成是一个只有引用没有实体的对象。接口的引用,实际是指向实现接口的类的普通对象。
接口与引用计数
接口是属于托管的类型,接口会被运行时自动管理,类对象被接口类型引用时,会进行引用计算的记录。