8.1 设计模式误用复盘


文档摘要

8.1 设计模式误用复盘 本节摘要:设计模式的误用比不用更贵:为简历写的抽象成了每次变更的税。本节从三个真实翻车(单例的隐藏依赖、模板方法的僵化、四层包装的策略模式)讲起,给出"三次法则"的引入纪律、常用模式在 Java 生态的现代形态(很多已被语言与框架吸收),以及评审时识别过度设计的信号。 翻车一:单例的隐藏依赖网 某规则引擎把十几个服务做成了单例(自研的 getInstance 风格),两年后的测试成了灾难:任何一个类单测都要拉起整个单例网络—— 内部构造时拉起 ,后者又连了配置中心。想测一个纯计算函数?先起半个世界。 单例模式的本质问题不是"只有一个实例",而是把依赖关系从构造期(可见、可注入)转到了运行期(隐藏、全局)。Spring 的 IoC 容器(第 7.

8.1 设计模式误用复盘

本节摘要:设计模式的误用比不用更贵:为简历写的抽象成了每次变更的税。本节从三个真实翻车(单例的隐藏依赖、模板方法的僵化、四层包装的策略模式)讲起,给出"三次法则"的引入纪律、常用模式在 Java 生态的现代形态(很多已被语言与框架吸收),以及评审时识别过度设计的信号。

翻车一:单例的隐藏依赖网

某规则引擎把十几个服务做成了单例(自研的 getInstance 风格),两年后的测试成了灾难:任何一个类单测都要拉起整个单例网络——RuleEngine.getInstance() 内部构造时拉起 ConfigService.getInstance(),后者又连了配置中心。想测一个纯计算函数?先起半个世界。

单例模式的本质问题不是"只有一个实例",而是把依赖关系从构造期(可见、可注入)转到了运行期(隐藏、全局)。Spring 的 IoC 容器(第 7.1 节)恰好治好了这个病:默认单例的作用域,但依赖全部显式注入,测试时换成 mock 即可。现代 Java 里手写单例的正当场景只剩下工具类与无状态纯函数集合(且静态方法更直白),有依赖的"单例"一律交给容器。

翻车二:模板方法的僵化

报表框架用模板方法定义骨架:父类里 generate() 固定了"取数-计算-渲染-上传"四步,子类覆写各步。三年后一个新需求只要"渲染+上传"(数据来自上游传入)——但模板的骨架动不了,子类被迫覆写"取数"为空操作、"计算"为透传。四个子类里两个是"空壳适配",看代码的人完全无法分辨哪些步骤是真实逻辑。

模板方法的软肋在第 2 章埋过伏笔:继承是强耦合,骨架一变全体震颤,而骨架无法预知所有未来变体。同样的需求用组合 + 函数式表达就灵活得多:

ReportBuilder.of(fetcher, renderer, uploader) // 步骤是注入的组件 .skipFetchingWhen(dataReady) // 变体是组合的选择 不是空覆写 .generate();

策略被提升为参数(Function/Consumer),骨架与变体的关系从继承变成装配——这正是 JDK 与 Spring 的演化方向:Comparator 的组合、Function 的链式、Spring 的 BeanPostProcessor 链,全是"模式被 API 化"的实例。

翻车三:四层包装的策略模式

新项目技术负责人要求"体现设计功底",一个简单的折扣计算被包装成:Strategy 接口 → 抽象模板 → 四个具体策略 → 工厂 → 上下文——改一个折扣规则要跨五个文件。而业务上折扣规则总共三条,且半年没变过。评审时的灵魂拷问:这三条规则如果用三个 if 写,多少行?二十行。用模式写,多少行?两百行,外加一层所有人都得学的间接。

模式引入的决策矩阵

模式引入的决策矩阵

语言与框架已经吸收了哪些模式

GoF 二十三个模式里,相当一部分在现代 Java 里有了更轻的原生形态,识别它们能避免"手写已被吸收的模式":

经典模式 现代形态 备注
单例 容器管理 Bean / enum 手写仅限无依赖工具类
策略 函数式接口注入 Lambda 即策略对象
模板方法 组合 + 函数参数 装配胜过继承
观察者 事件总线 / Stream / Reactor Spring 事件最常用
建造者 record + 紧凑构造 / Lombok 不可变优先
装饰器 java.io 的流家族是教材 新代码用组合包装
代理 JDK 动态代理 / CGLib 框架 AOP 的底 第3.3节
迭代器 for-each / Stream 语言内建

这不是"模式过时了",是模式的意图不变、实现载体升级了。真正该内化的是意图:策略是"变化点外置",观察者是"依赖倒置的通知版",装饰器是"不改原类加行为"。带着意图看新框架,处处是模式的转世。

评审中的克制条款

把"该不该抽象"变成评审可执行的条款:

  • 三次法则:同构变化出现第三次才提取抽象,前两次容忍重复——重复是可支付的利息,错误抽象是还不清的本金
  • 每个接口至少两个实现才立项(测试 mock 不算),单实现先用类,等第二个实现出现再提接口(IDE 一键提取,成本极低)
  • 间接层数设上限:追一个业务流程的跳转不超过五次(方法调用不含框架内部)
  • 抽象必须有名字能说清:"一个东西的部分行为在运行时可被替换"说不清就别建 XxxHandler
  • 死代码与空壳适配定期清(认识字段的工具任务),僵尸抽象比没有抽象更迷惑

⚠️ 常见坑:把"用了多少模式"当设计评审的加分项。评审该问的是反方向——这个抽象删掉会怎样?如果答案是"代码更直白",删。

💡 关键直觉:模式是重复问题的命名解,不是简历的关键词。先有三次真实的疼,再引入对应的药——顺序反过来,药本身就是病。

防坑清单

  • 有依赖的"单例"一律容器注入;手写单例限无状态工具
  • 变体表达优先组合与函数式,模板方法只用于骨架真正稳定的框架
  • 三次法则写进团队手册,评审时敢说"这里先重复着"
  • 学模式先学意图,识别框架里的转世形态,不手写已吸收的
  • 季度性清理空壳子类与单实现接口

本节要点回顾

  • 单例之病:依赖从构造期转入运行期,测试性崩坏,容器是正解
  • 模板之僵:继承耦合骨架,变体用组合与函数参数表达
  • 三次法则:容忍前两次重复,第三次再抽象,顺序不可颠倒
  • 模式演化:意图不变,载体被语言与框架吸收
  • 评审反向问:删掉这个抽象会怎样

下一节讲性能:怎么把"感觉慢"变成"定位到行"。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U