设计模式 22:两条变化维度如何分开
通知原本有收据与告警两类,能发邮件和短信。旧实现分别列出四种 kind:channel 组合。现在加一个推送渠道,仅沿着旧思路复制分支,就得在两类通知各加一次,下一次增加通知类型又要给全部渠道各加一支。变化来源分别是“发送什么”和“怎样送出”。
组合两侧,不替另一侧做决定
Before.send("alert", "push", "A") 直接拒绝;旧四种组合的文本却是明确合同。After 让 Receipt、Alert 各自生成内容,通过构造参数持有 Channel 实现;新 push 通道只提供一个 message -> "push|" + message,无需再造 ReceiptPush 与 AlertPush 两个子类。
1 | |
调用方向是通知对象决定内容 → 渠道实现负责交付的文本变换。调用者仍要选择“哪种通知 + 哪种渠道”,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。测试遍历四种旧组合并断言新推送可与两类内容组合,同时验证空订单短路。
- 新增“续费提醒”内容,在原邮件/短信/推送下写九种组合的断言,再加一种通知类型,不应修改既有渠道实现;复跑旧四种合同。
- 如果产品永远只有一种通知与一个渠道,去掉 Bridge,写一个直接返回字符串的函数;用测试说明删去抽象保留了什么、失去了什么。
参考资料
- GoF 原书公开图书馆 PDF,4.2 Bridge,目录页标注起始页 171:https://cpcc.chd.gov.in/Content/PDFs/w8UkV3tWNyEtsbUZSWJ7fVhuB9A3tsGYdm4w6VGzg2wUTNFYikqnvvFbbkiW2zmfspPEghd7QTamiMby3lVIBemrhdVWwt6rOQnm.pdf 。
- Bridge 模式出版社二手摘录,Pearson/InformIT:https://www.informit.com/articles/article.aspx?p=1398603 。
- Vlissides/Schmidt 公开课件
An Introduction to Design Patterns:https://www.dre.vanderbilt.edu/~schmidt/PDF/GoF.pdf 。 - 本篇来源边界:
writing-plans/design-patterns/SOURCES.md。 - 实验代码:
examples/design-patterns/labs/22/。
上一节:21 接口转换如何保留语义;下一节:23 组合商品如何统一报价。






