通知原本有收据与告警两类,能发邮件和短信。旧实现分别列出四种 kind:channel 组合。现在加一个推送渠道,仅沿着旧思路复制分支,就得在两类通知各加一次,下一次增加通知类型又要给全部渠道各加一支。变化来源分别是“发送什么”和“怎样送出”。

组合两侧,不替另一侧做决定

Before.send("alert", "push", "A") 直接拒绝;旧四种组合的文本却是明确合同。After 让 Receipt、Alert 各自生成内容,通过构造参数持有 Channel 实现;新 push 通道只提供一个 message -> "push|" + message,无需再造 ReceiptPush 与 AlertPush 两个子类。

1
2
3
Channel push = message -> "push|" + message;
String receipt = new Receipt(push).send("A");
String alert = new Alert(push).send("A");

调用方向是通知对象决定内容 → 渠道实现负责交付的文本变换。调用者仍要选择“哪种通知 + 哪种渠道”,Bridge 并没有自动消灭组装逻辑。Alternative 用两个小 switch 分开选择,品种封闭且只在一个地方组装时更短;After 更适合两侧由不同模块独立增加实现的需求。空订单在调用渠道前被拒绝;旧 Before 不具备这项新检查,不能把它算作共同旧合同。

与 21 的 Adapter 不同,Bridge 在设计时就承认两个独立扩展方向,而不是给已有不兼容协议做一次语义转换。它也不是 Proxy 的访问控制、Facade 的入口聚合或 Decorator 的行为叠加。没有真实推送、重试或通知送达保证,channel.deliver 仅返回字符串。

设计 增加推送渠道要动哪里 谁处理空订单
Before 增补多个组合分支 本例旧版未校验
After 注入一个新 Channel 通知入口拒绝
Alternative 添加渠道 switch 一支 统一入口拒绝
flowchart LR
    Receipt[Receipt] -.继承.-> Base[After 通知抽象]
    Alert[Alert] -.继承.-> Base
    Base --> Channel[Channel 发送接口]
    Channel --> Sender[调用方提供渠道实现]

验证与练习

在 examples/design-patterns/ 执行 ./mvnw -B -ntp -pl labs/22 -am test 与累计 ./mvnw -B -ntp verify;环境、输入与原始输出见 examples/design-patterns/evidence/22/RUN.md。测试遍历四种旧组合并断言新推送可与两类内容组合,同时验证空订单短路。

  1. 新增“续费提醒”内容,在原邮件/短信/推送下写九种组合的断言,再加一种通知类型,不应修改既有渠道实现;复跑旧四种合同。
  2. 如果产品永远只有一种通知与一个渠道,去掉 Bridge,写一个直接返回字符串的函数;用测试说明删去抽象保留了什么、失去了什么。

参考资料

上一节:21 接口转换如何保留语义;下一节:23 组合商品如何统一报价。