二十年前就想过这个问题,把一个 TButton 拖到 Form 上,双击时弹出的代码窗口 —— 为什么按钮知道该如何响应点击?如同第一次在舞会上被陌生舞伴握住手时,竟不知是谁先迈开的舞步。后来在调试器里跟踪调用栈才发现,原来按钮和窗体之间早有一份隐形契约:按钮负责感知触碰,窗体负责处理逻辑,而接口就是这份契约的文字版。
这便是今天我们想梳理的内容界面接上一个章节。继续理解窗口消息机制中的内容。接口最妙的地方,是让 "知其然" 和 "知其所以然" 完美分家。就像 ATM 机的键盘,用户只需知道按 1 是查询、按 2 是取款,无需了解内部的加密芯片如何工作。曾做过高校校园卡的系统时,我们用接口封装了身份证读卡器:前台窗口的操作员点 "读卡" 按钮时,不需要知道是用的串口读卡器还是 USB 读卡器,接口早把不同设备的差异藏在了背后。等厂商突然更换了读卡器型号,我们就需要花时间去完成了适配 —— 因为所有界面代码都遵守着接口契约,就像换了个舞者但舞曲没变。
一、宣告界面
宣告接口时的心情,很像新婚夫妇草拟婚前协议 —— 既要明确权利义务,又得留有余地。看这个定义过一个 IReport 接口:
type
IReport = interface
['{8A7B6C5D-4E3F-2G1H-0I9J-8K7L6M5N4O3P}']
function GetPatientInfo(id: string): TPatient;
procedure GeneratePDF(savePath: string);
function GetPageCount: Integer;
end;
当时觉得这三个方法已经包罗万象,但需要要加 "打印预览" 功能。看着接口里没有对应的方法声明,突然明白老程序员说的 "接口要像钻石一样恒久" 是什么意思 —— 一旦发布就不能轻易修改,否则所有实现它的类都得跟着改,就像修改舞谱会让所有舞者重新排练。最后我们新增了 IReportEx 接口继承自 IReport,这才明白接口也能像俄罗斯套娃一样层层嵌套。
那个被中括号裹着的 GUID,crtl+shift+G自动生成。最初不会生成时,手动敲了串数字,结果运行时两个接口莫名冲突,就像两个舞者戴着相同编号的胸牌参加比赛。其实这个GUID就像个指纹 —— 世界上没有两个完全相同的接口,就像没有两个完全相同的指纹。
二、实作和使用接口
1、型态转换
让类实现接口的过程,很像教孩子学规矩。举个例子,如果要设计一个图书馆管理系统时,我们让 TBook 类实现了 IBorrowable 接口:
type
TBook = class(TInterfacedObject, IBorrowable)
private
FTitle: string;
FIsBorrowed: Boolean;
public
constructor Create(title: string);
function Borrow(reader: string): Boolean;
procedure ReturnBook;
function GetStatus: string;
end;
当创建出 TBook 实例时,它就像个既懂礼貌又会算数的孩子 —— 既能用 IBorrowable 接口对外提供借阅功能,又能通过自身方法管理内部状态。刚工作时自己也想过:"为什么非要实现接口?直接调用 相应的 方法不行吗?" 后来知道了设计模式相关知识,也就豁然开朗了。就如同我们调用接口使用打印机,打印时会关心打印机是惠普还是佳能吗?接口就是打印机的型号。"
2、五种邀约方式
1. 直接型态转换
var
Book: TBook;
BorrowObj: IBorrowable;
begin
Book := TBook.Create('Delphi编程指南');
BorrowObj := IBorrowable(Book);
BorrowObj.Borrow('张三');
end;
这种方式适合内部系统,就像同事之间喊 "帮我复印文件" 那样直接。
2. 编译器的内建机制
var
BorrowObj: IBorrowable;
begin
BorrowObj := TBook.Create('Delphi编程指南');
BorrowObj.Borrow('李四');
end;
这是 Delphi 最讨喜的设计,就像小区门口的智能门禁 —— 你刷脸时不用告诉系统 "我是 3 号楼的"。用这种方式创建业务接口实例,省了上千行类型转换代码。这就是 "依赖接口而非实现" 的真谛。
3. TObject 的 GetInterface
var
Book: TBook;
BorrowObj: IBorrowable;
begin
Book := TBook.Create('Delphi编程指南');
if Book.GetInterface(IBorrowable, BorrowObj) then
BorrowObj.Borrow('王五')
else
ShowMessage('这本书不支持借阅');
end;
这种方式在处理插件时特别好用,就像去陌生人家做客先按门铃。记得我们让第三方厂商的组件通过 GetInterface 暴露功能,即使某些厂商没实现完整接口,系统也能优雅降级。有次演示时某个插件失效,系统只是显示 "该功能暂不可用",客户后来评价说:"你们的软件像个有教养的绅士。"
4. 对象的 QueryInterface 方法
var
Book: TBook;
BorrowObj: IBorrowable;
Guid: TGUID;
begin
Book := TBook.Create('Delphi编程指南');
Guid := IBorrowable;
if Book.QueryInterface(Guid, BorrowObj) = 0 then
BorrowObj.Borrow('赵六');
end;
这是 COM 时代的遗产,像国际通用的外交礼节。用 QueryInterface 与服务接口通讯 —— 虽然代码啰嗦,但在 Windows 系统上从未掉链子。记得有次设备厂商的工程师看到这段代码,突然说:"你们还在用这种老方法?" 我只能笑着说,五年没宕机的系统:"虽然陈旧,但不会错。"
5. as 操作数
var
Book: TBook;
BorrowObj: IBorrowable;
begin
Book := TBook.Create('Delphi编程指南');
try
BorrowObj := Book as IBorrowable;
BorrowObj.Borrow('孙七');
except
on E: EInvalidCast do
LogError('借阅失败:' + E.Message);
end;
end;
这种方式适合写用户操作流程,就像给派对请柬加了回执。记得杂移动互联网时代后来用别的语言开发,我们用 as 操作数处理不同分辨率的屏幕接口,即使遇到奇葩设备也能捕获异常并上传日志。有次分析错误日志发现,某款山寨平板的摄像头接口实现有问题,正是 as 操作数帮我们收集到了关键信息。
最后小结:
关于接口开发,似乎有很多说不完的护体,接口不仅是约束,更是自由 —— 它让我们能在真实世界准备好之前,先用虚拟的契约搭建系统的骨架。如同这二十多年从桌面软件写到移动应用,从单体系统拆成微服务,发现接口机制始终没变。就像老北京四合院的门环,不管里面住的是王公还是百姓,敲门的规矩总保持一致。其实是Delphi 的接口教会我的,不只是代码的组织方式,更是一种协作哲学:明确边界但保持弹性,遵守契约但留有空间。
如今的编码技术都都用注解和依赖注入了,但打开调试器,可以接口变量还是像接力棒一样在不同模块间传递。是的,技术在变化,但就像接口本身一样 —— 不需要解释,只需要实现。未完待续.....