当前位置: 首页 > news >正文

你每用一个设计模式,可能就多了一个过度设计

设计模式教程看多了,容易得一种病:看什么代码都觉得需要重构,重构就想套模式。结果一个 CRUD 接口搞出五个类三个接口,新人接手代码先看半小时类图才能找到入口。

我见过最离谱的案例:一个内部工具的配置模块,总共 3 个配置项,被人用策略模式 + 工厂模式 + 模板方法模式重构了一遍。重构前一个 switch 搞定,重构后 8 个类 2 个接口。说是"开闭原则",结果 3 个月里需求没变过一次,但每次加配置项要改 4 个文件。

今天不聊怎么用设计模式,聊聊什么时候不该用。

单例模式的温床:全局变量披着面向对象的外衣

单例模式是最容易被滥用的模式,因为它的卖点是"全局唯一",这跟全局变量的卖点一模一样。

```java // 这种单例跟全局变量有什么区别? public class AppContext { private static AppContext instance = new AppContext();

private Map<String, Object> attributes = new HashMap<>(); public static AppContext getInstance() { return instance; } public void setAttribute(String key, Object value) { attributes.put(key, value); } public Object getAttribute(String key) { return attributes.get(key); }

} ```

这不是单例模式,这是HashMap换了件衣服。但它比HashMap更危险,因为单例模式给了你一种"我做了设计"的错觉。

单例真正需要用的场景很少:连接池、线程池、全局配置。如果你的单例只是个数据袋(data bag),大概率不需要单例,需要的是重新审视你的依赖关系。

更糟糕的是单例 + 可变状态。一个有状态的 Singleton 在测试中是噩梦——你不能隔离测试,因为状态是全局的,上一个测试用例修改的状态会影响下一个。

工厂模式的接口爆炸

工厂模式的问题是:每加一个产品,要加一个实现类。这在产品类型多的时候没问题,但产品类型少的时候就是负担。

```java // 只有两个通知渠道,用工厂模式值得吗? public interface NotificationSender { void send(String message, String recipient); }

public class EmailSender implements NotificationSender { ... } public class SmsSender implements NotificationSender { ... }

public class NotificationSenderFactory { public NotificationSender create(String type) { switch (type) { case "email": return new EmailSender(); case "sms": return new SmsSender(); default: throw new IllegalArgumentException(); } } } ```

3 个接口/类 + 1 个工厂类 = 4 个文件,处理 2 个渠道。直接写两个 Service 类 + 一个 if-else 是 2 个文件。

你可能会说"以后会加渠道"。但问题是:你确定以后会加吗?很多项目的"以后会加"永远没来,但工厂的维护成本从第一天就开始了。

我的判断标准:少于 3 个实现类的接口,不要急着建工厂。等第 3 个实现类真的出现的时候再重构,成本很低。

策略模式只有 2 个策略时的尴尬

策略模式的卖点是用组合替代继承,避免 if-else 膨胀。但如果只有 2 个策略,效果适得其反:

```java // 只有两个策略 public interface DiscountStrategy { BigDecimal calculate(BigDecimal price); }

public class NormalDiscount implements DiscountStrategy { public BigDecimal calculate(BigDecimal price) { return price.multiply(new BigDecimal("0.9")); } }

public class VipDiscount implements DiscountStrategy { public BigDecimal calculate(BigDecimal price) { return price.multiply(new BigDecimal("0.8")); } }

// 使用方 DiscountStrategy strategy = isVip ? new VipDiscount() : new NormalDiscount(); strategy.calculate(price); ```

对比直接写:

java BigDecimal finalPrice = isVip ? price.multiply(new BigDecimal("0.8")) : price.multiply(new BigDecimal("0.9"));

策略模式的版本多了一个接口、两个实现类,使用方的代码也没少多少(还是有个三元表达式)。如果策略逻辑只有一行,模式带来的复杂度已经超过了它解决的问题。

策略模式的收益跟策略数量正相关。2 个策略时收益约等于 0,3-4 个时开始值得,5 个以上时明显赚。别在 2 个策略的时候就急着上模式。

观察者模式的调试地狱

观察者模式的坑在调试的时候才会暴露。你断点打在业务方法上,发现某个状态不对,但不知道是谁改的——因为 Observer 链里有 5 个监听器,每个都可能触发状态变更。

java // 看着很优雅,调试的时候想骂人 eventBus.register(new OrderCreatedListener()); // 监听订单创建 eventBus.register(new InventoryUpdateListener()); // 更新库存 eventBus.register(new PointsUpdateListener()); // 更新积分 eventBus.register(new NotificationListener()); // 发通知 eventBus.register(new AnalyticsListener()); // 数据统计

5 个监听器,5 个类,可能分布在 5 个包里。一个eventBus.post(orderCreatedEvent)调用下去,你要跳 5 次才能跟完整个链路。更惨的是,如果其中一个监听器又 post 了新事件,整个调用链变成了一棵树。

我的做法:观察者链路超过 3 层就必须画时序图。不是因为文档需要,是因为你自己也会忘。

另外,观察者模式的异常处理是个大坑:如果 Listener3 抛异常了,Listener4 和 Listener5 还要不要执行?如果执行,状态可能不一致;如果不执行,一个无关的监听器故障会阻塞整个事件链。

Guava EventBus 的做法是:每个监听器独立 try-catch,互不影响。但这也意味着一个监听器的异常不会被上层感知到。如果你需要"任何一个监听器失败都回滚"的语义,EventBus 做不到,得用别的方式。

真实案例:过度设计→回归简单

之前接手过一个项目的权限模块,前任开发者做了这样的设计:

  • PermissionChecker(策略模式)→ 4 个策略实现
  • PermissionProvider(工厂模式)→ 2 个工厂
  • PermissionEvent(观察者模式)→ 3 个监听器
  • PermissionCache(装饰器模式)→ 2 个装饰器

总共 15 个类文件。权限规则呢?就一条:管理员全部放行,其他用户按角色判断

重构后:

java public class PermissionService { public boolean check(String userId, String resource) { if (isAdmin(userId)) return true; return roleMapper.hasPermission(userId, resource); } }

1 个类,1 个方法。原来 15 个类干的事情,1 个类全干完了。

前任开发者不是能力不行,他是被设计模式洗脑了——觉得不用模式就不算设计。但设计的目标是解决问题,不是展示模式。

几个实用判断标准

  1. 实现类 < 3 个的接口,别急着建工厂。等实现类真的多了再重构,接口定义不会变。

  2. 策略逻辑 < 5 行的策略,别用策略模式。用 if-else 或 switch 就行,策略模式带来的间接性成本超过了代码膨胀的收益。

  3. 观察者链路 > 3 层的,画时序图。不画你自己都会迷路。

  4. 单例持有可变状态的,先问能不能用依赖注入替代。Spring 管理的 Bean 默认就是单例,不需要自己实现。

  5. 如果"开闭原则"是你用某个模式的唯一理由,再想想。开闭原则是为了应对变化,如果变化不会来,你提前开的闭就是技术债。

设计模式是工具,不是信仰。工具该用的时候用,不该用的时候别硬上。代码的衡量标准不是"用了几个模式",而是"新来的人看多久能看懂"。


我在做一个用卡皮巴拉讲设计模式的微信小程序「爪爪代码冒险记」,23 个设计模式用漫画 + 答题的方式讲,感兴趣可以搜一下看看。

http://www.jsqmd.com/news/1004270/

相关文章:

  • 干细胞:探索生命种子的神秘面纱
  • 2025企业AI落地行动指南:聚焦价值流穿透与运营杠杆转化
  • 东昌府区黄金回收实体店探访 - 润富黄金回收
  • 【郴州同城黄金回收服务 | 万金汇黄金回收】 - 润富黄金回收
  • 自媒体账号RPA 自动发布技术实现,本文主要针对平台方使用Quill 编辑器,其他编辑器也可以使用类似方案处理!
  • 2026年合肥注册公司服务商怎么选?本地化财税机构能力解析与真实案例参考 - 优质品牌商家
  • 2026年安庆市黄金回收白银回收铂金回收彩金回收 地址联系大全+支持现场结算无套路 - 前途无量YY
  • 封神榜风格横版游戏源码:含角色选择、登录界面与基础场景管理(Cocos2d-x 2.x/3.x)
  • SpringBoot项目里调用老旧C# WebService接口,我是怎么用HttpClientBuilder一步步搞定的?
  • 自适应系统中的运行时伦理挑战与技术应对
  • 鸿蒙原生应用实战(二):游戏库列表与筛选排序 — 卡片式UI设计
  • 基于Osip的Windows SIP通信双工程示例:发送INVITE/REGISTER与接收响应一体化封装
  • 2026番禺区新造下水道疏通技术办案逻辑解析:居顺联疏通服务深耕本地厨卫下水疏通 - 居顺联家政疏通
  • Vue 3 中的事件监听问题及解决方案
  • 2026年杭州软考中级系统集成报名费用资料怎么确认?众智商学院官网400冯老师 - 众智商学院官方
  • HLS性能翻倍的秘密:深入解读`array_partition`、`pipeline`与`dataflow`三大优化指令(附Vitis HLS 2023.2实测数据)
  • 微信小程序蓝牙开发避坑实录:从连接失败到数据收发,我踩过的那些坑
  • ArcGIS地统计向导实战:用普通克里金法预测石家庄房价(附趋势剔除与Log变换技巧)
  • 【郴州同城黄金回收服务 | 鑫诚黄金回收】 - 润富黄金回收
  • 2026年射洪装修公司怎么选?从本地经验、材料体系到售后保障的多维度分析 - 优质品牌商家
  • 读UNIX传奇:历史与回忆01贝尔实验室
  • LLM工程落地五大关键技术闭环解析
  • 大功率工业吸尘器十大品牌2026排名,第一名实至名归 - 工业清洁测评社
  • 【郴州同城黄金回收服务 | 鑫盛鑫诚万金汇联合回收指南】 - 润富黄金回收
  • 科研绘图效率翻倍:用ArcGIS+AI组合拳,5分钟搞定论文地图的精修与排版
  • 告别版本兼容烦恼:用Python mikeio 1.x新版搞定ERA5风场转MIKE21 dfs2文件
  • 别再死记硬背了!用这个可视化工具,5分钟搞懂‘图序列’判定定理
  • 2026年安丘市黄金回收白银回收铂金回收彩金回收 地址联系大全+支持现场结算无套路 - 前途无量YY
  • 2026济南历下蒂芙尼回收|弄懂估价逻辑,出手首饰少花冤枉钱 - 逸程
  • 别再让3D模型拖慢你的网页了!Three.js + Blender纹理烘焙实战避坑指南