18.9 插件

在配置期而非编译期连接实现类,让部署环境选择符合接口的具体行为。

学习目标

  • 能解释插件如何把实现选择推迟到配置期,并画出核心、接口、配置与实现之间的依赖方向
  • 能编写 TypeScript 插件注册与加载代码,验证实现遵守接口并让加载失败保持可见
  • 能根据变化轴、版本约束、测试隔离和回退成本,判断何时采用或拒绝插件

为什么 18.9 插件 值得单独学习

在配置期而非编译期连接实现类,让部署环境选择符合接口的具体行为。18.9 插件的核心不是套用某个框架 API,而是回答:如何把可替换实现隔离在稳定契约之后,让核心代码依赖语义而不是某个部署环境。在订单系统基础设施替换中,如果无法说清责任、状态和失败由谁承担,即使配置能加载,架构决定也没有完成。

的价值不是“任何东西都可以动态加载”,而是把明确的变化轴放进受控边界。本页以 2024 年中文版公开目录限定 18.9 插件 的范围,并依据 Martin Fowler 的作者图书页和模式目录独立重写。它不复现原书正文、插图或代码;目录只决定“要讲什么”,这里的案例、实验、判断题和答案均为本课程原创。

先建立直觉:问题、机制与代价

18.9 插件面对的具体压力是“实现替换频率、部署差异、版本约束与测试隔离成本”。它采用的机制可以概括为:核心只依赖接口,配置在装配阶段选择实现,插件实现通过注册表接入。机制带来的收益必须与新增间接层、加载失败、兼容性检查和回滚成本同时记录,否则学习者只会得到一个没有拒绝条件的模式名称。

对 18.9 插件,优先比较的替代路线是:直接调用稳定实现、策略对象或显式依赖注入。只有在变化轴确实跨越部署边界,且每个实现都能遵守 时,才值得引入插件;若实验只能显示结果而不能指出配置从何处选择实现,就不能据此选择 18.9 插件。

目录单元到教学证据

18.9 插件

18.9 插件 的学习边界里,18.9 插件不是待背诵的目录词,而是用来检查“核心代码”是否把实现选择交给正确的装配边界。对订单系统基础设施替换,学习者要记录配置、版本和依赖方向的可观察变化,并说明它何时支持或否定 18.9 插件。

专属设计案例:订单系统税费基础设施替换

把订单系统税费基础设施替换切成“核心调用者 → 税费接口 → 配置注册表 → 插件实现 → 结果”五个观察点。18.9 插件的设计草案必须写出谁拥有配置、谁验证版本、失败怎样传播,以及依赖方向、对象语义、配置、测试隔离、表示转换中哪个指标最先提示当前方案不再适用。

设计记录采用五个可换行字段:单元键为 poeaa24-pattern-49-plugin;模式族为 base;裁决是“能通过配置切换插件、拒绝不兼容实现,并记录加载失败与版本约束”;观测项包括依赖方向、对象语义、配置、测试隔离、表示转换;拒绝条件是“没有真实变化轴,或插件契约无法被自动验证”。

配置不是生产框架语法,而是一张评审卡。对 18.9 插件的任何实现都要能把运行证据重新映射到这张卡;如果更换 ORM、Web 框架或部署平台后无法回答同一组问题,说明决定依赖的是工具偶然行为而不是模式语义。

结构解剖

Plugin:运行时装配,核心不感知具体实现核心代码taxCalc.compute()只依赖接口TaxCalculatorinterface compute(order)ChinaTax(配置选择)USTaxEUTax(新增无需改核心)plugin.properties 决定装配核心价值:• 运行时通过配置选择实现 • 新增插件不改核心代码 • 开闭原则的落地手段核心只依赖接口,具体实现通过配置在运行时装配
Plugin 通过配置文件或注册机制在运行时选择实现, 核心代码只依赖接口,新增实现无需修改核心。

选择与拒绝矩阵

评审问题选择 18.9 插件 的证据应拒绝或改用其他方案的信号
责任核心只依赖稳定接口,装配边界拥有实现选择业务代码读取配置并自行实例化多个实现
变化新实现可独立发布且不改核心调用者每加入一个实现都要修改核心分支
失败缺少插件、版本不兼容和运行错误可分别观测加载失败被静默回退成错误实现
替代已与策略对象、依赖注入等方案比较并保留回滚路径因为“以后可能替换”就预先增加全局注册表

代码实践:安全加载一个税费插件

订单系统可以在部署配置中选择税费实现,但核心调用者只面对 TaxCalculatorloadPlugin 的责任是检查名称和版本约束;它不能把缺少插件伪装成零税率。必须由测试和启动检查共同守住。

type Order = {
  country: string;
  subtotalCents: number;
};
 
interface TaxCalculator {
  readonly version: string;
  compute(order: Order): number;
}
 
type AvailablePlugin = {
  key: string;
  implementation: TaxCalculator;
};
 
class ChinaTax implements TaxCalculator {
  readonly version = "1";
 
  compute(order: Order): number {
    return Math.round(order.subtotalCents * 0.13);
  }
}
 
class USTax implements TaxCalculator {
  readonly version = "1";
 
  compute(order: Order): number {
    return Math.round(order.subtotalCents * 0.08);
  }
}
 
function loadPlugin(
  key: string,
  requiredVersion: string,
  available: AvailablePlugin[],
): TaxCalculator {
  const entry = available.find((candidate) => candidate.key === key);
  if (!entry) throw new Error(`plugin not found: ${key}`);
  if (entry.implementation.version !== requiredVersion) {
    throw new Error(`incompatible plugin version: ${key}`);
  }
  return entry.implementation;
}
 
const configuredTax = loadPlugin("cn-tax", "1", [
  { key: "cn-tax", implementation: new ChinaTax() },
  { key: "us-tax", implementation: new USTax() },
]);
 
const order = { country: "CN", subtotalCents: 10000 };
const taxCents = configuredTax.compute(order);

这段代码把实现选择留在装配边界,把业务计算留在插件契约内。启动时找不到插件或版本不兼容会明确失败;测试则可以用一个记录调用的假实现验证核心调用者,而不必连接真实税务服务。

常见误区

可验证练习

练习

问题 1:识别边界。 核心订单服务直接读取环境变量并实例化 ChinaTax,这仍然是插件结构吗?

问题 2:处理兼容性。 配置选择了版本 2 的插件,而核心只支持版本 1。应该返回零税率还是继续启动?

问题 3:决定是否使用。 只有一个实现、没有部署差异,团队仍想引入插件以“方便未来替换”。应如何评审?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

插件

在配置期或运行期选择并装配、且遵守共同契约的可替换实现。

兼容契约

插件与核心共同遵守的输入、输出、错误和版本约束。

装配边界

解析配置、验证版本并把具体实现交给核心调用者之前的启动边界。

本章小结

掌握 18.9 插件的标志不是记住定义,而是能在订单系统税费基础设施替换中解释“核心调用者 → 税费接口 → 配置注册表 → 插件实现 → 结果”的责任链,利用依赖方向、对象语义、配置、测试隔离、表示转换作出可证伪的选择,并在没有真实变化轴或兼容契约无法验证时明确拒绝插件化。

前后导航

来源与改写范围

资料与写作方式声明

本章以Martin Fowler《企业应用架构模式》与公开模式目录权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…