接口是模块化插件系统的契约语言,约定能力而非实现;需聚焦行为、用业务语义命名、参数返回值简洁;配合多态注册机制实现动态加载;扩展生命周期钩子保障资源安全;辅以类型安全与运行时校验确保契约落地。

接口是模块化插件系统真正的“契约语言”——它不规定怎么实现,只约定必须提供什么能力、接收什么输入、返回什么结果。只要插件遵守这个契约,主程序就能加载、调用、替换它,而无需关心内部逻辑。
接口定义要聚焦行为,而非技术细节
一个有效的插件接口应描述“能做什么”,而不是“怎么做的”。比如在图像标注场景中,Autodistill 的 BaseModel 只要求实现 predict(input) → output,不约束模型是 ONNX 还是 PyTorch,也不指定预处理方式。这种抽象让 YOLO、SAM、CLIP 等不同技术栈的模型都能接入同一套流程。
- 避免暴露实现类、路径、配置格式等内部信息
- 方法名使用业务语义(如
extractFeatures、validateInput),而非技术术语(如runInferenceSession) - 参数和返回值尽量用简单类型(string、number、dict/list)或明确定义的数据结构(如
Ontology类)
统一契约 + 多态注册,支撑动态发现与加载
接口本身只是静态约定,真正让插件“活起来”的是注册机制。.NET 和 Java SPI 都采用“接口 + 元信息文件”组合:主程序扫描插件目录,读取 META-INF/services/xxx 或 DLL 中导出的 IPlugin 实现,再通过反射或依赖注入实例化。关键在于——所有插件都实现了同一个接口,主程序才能用同一段代码遍历调用。
- .NET 中通过
Assembly.LoadFrom()加载 DLL,再用GetTypes().Where(t => t.Implements<IPlugin>())找出可用插件 - Java 中
ServiceLoader.load(MyPlugin.class)自动解析META-INF并构造实例 - TypeScript 项目常用默认导出对象,含
name和setup(app),主程序统一调用而不判断类型
生命周期方法是契约的延伸
基础功能接口之外,成熟插件系统通常扩展出标准生命周期钩子,如 initialize()、start()、stop()、destroy()。这些不是可有可无的装饰,而是保障资源安全的关键契约部分。例如 mplugin 要求插件提供 initialize 和 terminate 函数,主程序在加载后立即调用前者,在卸载前强制调用后者释放句柄或关闭连接。
- 没有
destroy的插件可能造成内存泄漏或文件句柄占用 - 没有
start/stop控制的插件难以支持热启用/禁用 - 建议在接口文档中标注各方法的调用时机与线程上下文(如“仅主线程调用”)
类型安全与运行时校验双保险
契约光靠文档不够,需要工程化保障。TypeScript 用 interface Plugin 声明结构,编辑器实时提示缺失字段;Python 可借助 Protocol 或抽象基类(如 Autodistill 的 @abstractmethod)在运行时报错;Java 则靠编译期接口检查加 SPI 元数据验证。
- 构建阶段加入插件合规性检查脚本(如验证导出对象是否含必要字段)
- 主程序加载插件时做轻量校验:
if typeof plugin.setup !== 'function' - 错误信息明确指向契约条款,例如:“插件 ‘logger’ 缺少 required 方法 ‘teardown()’,请参考 v2.3 插件规范”

















