Java 设计模式(策略 / 适配器 / 模板方法 / 工厂方法 / 建造者)

张开发
2026/4/13 6:58:27 15 分钟阅读

分享文章

Java 设计模式(策略 / 适配器 / 模板方法 / 工厂方法 / 建造者)
文章目录前言1. 策略模式1.1 从业务痛点开始为什么一定会遇到策略模式1.2 结构推导三层角色怎么分工1.3 进阶案例支付路由与优惠计算双策略组合1.4 常见反模式非常重要1.5 测试设计如何让策略模式可验证1.6 评审清单1.7 面试/分享高频问答2. 模板方法模式2.1 模式本质流程稳定步骤变化2.2 设计原则2.3 实战案例文件导出平台2.4 异常与事务处理建议2.5 反模式2.6 模板方法 策略组合2.7 测试策略3. 工厂方法模式3.1 为什么“new”会成为技术债3.2 结构理解3.3 业务案例多存储后端切换3.4 工厂方法与依赖注入Spring关系3.5 反模式3.6 落地建议3.7 与抽象工厂区别4. 适配器模式4.1 业务背景系统演进必然出现“接口断层”4.2 设计重点适配器不仅做参数转换4.3 实战案例统一对象存储接口接入三家云厂商4.4 反模式与风险4.5 类适配器 vs 对象适配器4.6 和外观模式区别容易混淆4.7 迁移路线建议5. 建造者模式5.1 业务痛点构造函数参数灾难5.2 推荐实现规范5.3 实战案例查询请求对象构建5.4 Builder 与 Lombok Builder5.5 反模式5.6 与工厂方法的关系5.7 测试与评审清单6. 结尾前言分享下项目经常用到的几种策略模式完整的系统这几种策略模式一般都是会同时用到的特别是模版方法策略适配的组合编码试着从自己项目中找到这几类设计模式的应用吧。1. 策略模式1.1 从业务痛点开始为什么一定会遇到策略模式真实项目里最常见的坏味道是一个“核心服务类”塞满分支if (type A) ... else if (type B) ...。一开始只有两个规则时你会觉得非常快甚至觉得“没必要抽象”。但半年之后业务变成 A1/A2/B1/B2/灰度实验规则再加上渠道、用户等级、地区、节假日分支会呈乘法爆炸。这种爆炸有三个直接后果1发布风险上升你改 B 规则A 规则也可能被影响2认知成本上升新人不敢改这个类3测试成本上升一个方法里几十条路径边界覆盖几乎不可能。策略模式本质上是“把变化点从主流程剥离出去”。主流程只负责“什么时候用策略”不负责“策略内部怎么算”。这样业务扩展就从“改旧代码”变成“新增策略类 注册策略”风险可控很多。1.2 结构推导三层角色怎么分工策略模式看起来只是接口实现但真正好用的版本通常包含四层Strategy定义统一能力边界ConcreteStrategy实现一个规则Context持有并执行策略StrategyRegistry可选把策略和业务 key 绑定。很多团队只写前三层后期仍然会写一段if-else来选择策略这会让“策略模式只完成一半”。建议把“选择策略”也工程化可以用 Map 注册可以结合 SpringComponent Qualifier可以按配置中心动态切换灰度策略非常常见。1.3 进阶案例支付路由与优惠计算双策略组合在电商下单里通常有两类变化路由策略走哪个支付网关价格策略按什么规则计算最终价。这两个变化维度不要混在一个策略里应该拆分为两个策略族然后在OrderCheckoutContext中组合。这样你可以得到“网关策略 * 价格策略”的灵活组合而不是写成上百个 if 分支。你在分享时可以强调一句策略模式不是只有一个策略维度多个独立变化点可以多个策略族并行。1.4 常见反模式非常重要1伪策略只是把 if 分支挪到策略类里选择时仍然写 if。2策略泄漏上下文策略实现频繁访问数据库、远程接口导致不可预测。3策略职责过大一个策略里既做校验、又做转换、又做持久化。4策略无版本管理线上灰度策略回滚困难。修复建议给策略输入输出做明确 DTO策略只做“规则计算”IO 留给外层策略命名标准化如VipPriceV2Strategy把策略选择日志打全便于审计和排障。1.5 测试设计如何让策略模式可验证策略模式的测试最容易做“单策略单测”但分享里要强调两层测试单策略单测验证每个策略在边界输入下结果正确选择逻辑测试验证 key - strategy 的映射正确集成测试验证策略组合例如支付策略 优惠策略对账一致。建议再加一条把历史线上真实输入做成回放样本防止规则升级出现“静默漂移”。1.6 评审清单变化点是否独立且稳定新增策略是否无需改旧策略代码是否存在策略选择日志与埋点错误策略是否有兜底策略NullObject/Fallback策略是否被过度拆分导致类过多难维护1.7 面试/分享高频问答问策略和状态模式区别答策略是外部选择算法状态模式是对象内部状态驱动行为切换。问策略是不是一定比 if 好答不是。分支少且稳定时if 更直接变化频繁才值得上策略。问策略很多怎么治理答注册中心 命名规范 统一监控 生命周期管理。2. 模板方法模式2.1 模式本质流程稳定步骤变化模板方法模式并不是“为了继承而继承”它解决的是流程型业务的稳定性问题。当你有明确、长期稳定的主流程例如“校验 - 处理 - 记录 - 通知”但每个步骤的细节会因渠道或场景不同而变化模板方法非常适合。如果你把流程写在每个子类里复制粘贴最终会出现步骤顺序不一致、审计漏打、异常处理不统一等线上事故。2.2 设计原则模板方法一般final防止子类破坏流程骨架可变步骤用abstract钩子步骤hook给默认实现子类可选覆盖公共前后处理日志、审计、异常包装放父类统一。2.3 实战案例文件导出平台导出主流程固定1参数校验2权限验证3查询数据4格式化CSV/Excel/PDF5上传文件6回写任务状态。变化点在“格式化”和“上传策略”父类控制大流程子类只做差异化。这可以显著避免“某个导出类型忘记鉴权”的高风险问题。2.4 异常与事务处理建议模板方法很适合统一异常语义子类抛业务异常父类转换为统一错误码父类决定是否重试、是否补偿父类统一写审计日志。如果流程涉及事务边界建议父类控制事务入口子类只提供纯业务步骤避免事务分裂。2.5 反模式1父类过胖父类知道太多业务细节演变成“上帝类”。2模板不稳定流程本身经常改说明抽象时机太早。3强行继承其实更适合组合策略却硬上模板方法。4钩子滥用钩子过多导致执行路径不透明。修复思路父类只放稳定流程和公共横切不稳定步骤转为策略注入钩子数量控制必须配文档。2.6 模板方法 策略组合强烈建议在分享中讲这个组合模板方法控制大流程策略负责步骤内部算法。这在支付、风控、审批系统里非常常见也比纯继承更灵活。2.7 测试策略父类流程测试验证步骤顺序可用 spy/mock子类步骤测试验证每个变化步骤边界异常流程测试验证父类兜底和审计必达。核心目标是“流程正确 变化可控”。3. 工厂方法模式3.1 为什么“new”会成为技术债业务代码里直接new具体类短期快长期会让调用方与实现强耦合。一旦你要切换实现比如短信服务从供应商 A 切到 B就得全局搜索替换风险极高。工厂方法模式就是把“创建逻辑”独立出来让调用方依赖抽象产品而非具体产品。3.2 结构理解Product产品抽象ConcreteProduct具体产品Creator工厂抽象定义工厂方法ConcreteCreator具体工厂决定创建哪种产品跟简单工厂相比工厂方法把“分支创建逻辑”分散到子类避免一个超级工厂不断膨胀。3.3 业务案例多存储后端切换你有FileStore接口具体实现LocalFileStore、S3FileStore、MinioFileStore。如果直接new业务层到处耦合具体实现。改成工厂方法后业务层只拿FileStoreFactory#create()结果使用。切环境、切云厂商、灰度迁移都更自然。配合配置中心后甚至可以按租户选择不同工厂实现。3.4 工厂方法与依赖注入Spring关系很多人问“用了 Spring 还需要工厂吗”答案需要场景不同。DI 解决对象生命周期与注入工厂方法解决“业务语义上的创建决策”。例如同是Notifier你可能要按用户偏好或故障熔断结果选择不同实现这仍然是工厂职责。3.5 反模式1工厂只是 new 一下没有任何创建策略价值。2工厂和产品一一对应但无扩展预期徒增类数量。3工厂里又塞 if-else回到简单工厂老路。4调用方仍然 instanceof 判断具体实现抽象失效。3.6 落地建议把创建策略写成配置化规则工厂输出统一抽象不向外暴露具体类型新增产品必须新增工厂测试工厂日志记录“为什么选了这个实现”。3.7 与抽象工厂区别工厂方法创建“一个产品等级结构”对象。抽象工厂创建“一组相关对象产品族”。分享时可一句话一个工厂一个产品 vs 一个工厂一套产品。4. 适配器模式4.1 业务背景系统演进必然出现“接口断层”适配器模式最常见于系统升级阶段老系统 SDK 仍在用新平台定义了统一能力接口。你不可能一次性重写所有存量系统也不能强行让业务方理解 N 套第三方协议。这时候最优解不是“直接改业务代码调用旧 SDK”而是建立统一接口再通过适配器把旧 SDK 包进去。这样业务层感知的是标准接口兼容成本被锁在适配层。4.2 设计重点适配器不仅做参数转换很多同学把适配器理解为“改下参数名”实际上生产级适配器要做四类转换1数据结构转换DTO/字段映射2语义转换返回码-异常null-空对象3时序转换同步调用改成异步回调或反之4可靠性转换重试、幂等、超时兜底。也就是说适配器是“协议翻译层”不是简单 wrapper。4.3 实战案例统一对象存储接口接入三家云厂商假设你定义ObjectStorageClient接口put/get/delete。阿里云、腾讯云、AWS 的 SDK 入参、异常、签名方式都不一致。做法每家写一个 Adapter统一转换为平台标准异常和统一元数据结构。收益上层业务只依赖平台接口后续新增厂商只加一个适配器类不改业务代码。4.4 反模式与风险1适配器中塞业务逻辑本应在服务层的规则被塞进 adapter边界污染。2适配器过深链路Adapter 套 Adapter排查时很痛苦。3错误语义未统一有的返回 false、有的抛异常调用端误判。4忽略可观测性出问题不知道卡在哪一层。治理建议适配层统一错误码/异常体系每个适配器增加请求耗时与失败率指标统一 trace tagadapterxxx适配器单测必须包含第三方返回异常/超时场景。4.5 类适配器 vs 对象适配器Java 中优先对象适配器组合因为单继承限制导致类适配器扩展受限组合更容易做运行时替换和 mock组合更适合 Spring 依赖注入。除非你有强约束且继承关系极清晰否则不建议类适配器。4.6 和外观模式区别容易混淆适配器为“接口不兼容”而生。外观为“简化复杂子系统调用”而生。一句话适配器解决“能不能接上”外观解决“好不好用”。4.7 迁移路线建议1先定义标准接口2给旧系统写 adapter3新代码只能依赖标准接口4逐步替换旧调用5最后删除历史直连路径。这就是典型“绞杀者迁移”策略风险低、可渐进。5. 建造者模式5.1 业务痛点构造函数参数灾难当对象参数超过 5 个尤其包含多个可选参数时构造函数会变得不可读。new Task(A, true, 3, null, csv, 100, false, x)这种调用几乎无法一眼看懂。更危险的是参数顺序错位编译期还可能不报错类型刚好兼容上线后出现隐蔽 bug。Builder 的核心价值是“可读、可校验、可扩展”。5.2 推荐实现规范外部类字段final尽量不可变Builder 提供链式设置build()做完整校验提供合理默认值必填参数可放 Builder 构造器中强制输入。5.3 实战案例查询请求对象构建复杂查询常包含分页、排序、过滤、时间区间、租户、数据权限、超时策略。这些参数在不同调用场景差异很大Builder 可以避免“重载构造器爆炸”。你还可以给 Builder 增加语义化方法forTenant()、withPermissionScope()让业务代码更像 DSL读起来更直观。5.4 Builder 与 LombokBuilderLombok 可以快速生成 Builder但分享时建议提醒生成快不等于设计好必填校验、跨字段约束比如开始时间 结束时间仍需手写复杂对象建议保留手写 Builder表达业务语义更清晰。5.5 反模式1简单对象也用 Builder过度工程化。2可变对象 Builder 混用创建后仍然随意 set失去价值。3build 不校验把错误延迟到运行时深处。4Builder 直接泄漏内部可变集合线程安全与封装问题。5.6 与工厂方法的关系两者不冲突常常组合使用工厂方法决定“创建哪一类对象”Builder 负责“把这个对象如何组装完整”。例如工厂先选中ExcelExportTask再用 Builder 设置分页、压缩、重试、输出目录。5.7 测试与评审清单build()是否覆盖必填参数校验默认值是否符合业务预期构建后对象是否不可变字段间约束是否在构建期验证链式 API 命名是否可读、无歧义6. 结尾如果今天只记住一句话模式不是为了“看起来高级”而是为了降低变化成本。策略解决算法变化适配器解决兼容问题模板方法解决流程复用工厂方法解决创建解耦建造者解决复杂对象构建。在真实项目里它们往往不是单独出现而是组合使用。你们做技术方案时不必先问“用什么模式”而要先问“变化点在哪里、风险在哪里、如何最小代价演进”。只要能回答这三个问题你就已经在用设计模式思维做工程了。

更多文章