设计模式总结与对比:创建型、结构型、行为型模式的核心区别与适用场景
学完23种设计模式,很多人会问:到底怎么选? 说实话,我当年刚学完时也懵。每种模式都懂,但一遇到真实需求就犯选择困难症。后来带团队做重构,踩了不少坑,才慢慢摸清门道。
今天这篇总结,我不打算罗列概念。咱们换个角度——从“你到底想解决什么问题”出发,把三类模式掰开揉碎讲清楚。
一句话记住三类模式:
- 创建型:帮你把“对象怎么造”这件事封装起来,调用方不用管 new 的细节。
- 结构型:帮你把“类和对象怎么组合”设计好,让系统更灵活、更易扩展。
- 行为型:帮你把“对象之间怎么协作、怎么分配职责”理清楚,降低耦合。
一、创建型模式:把“new”这件事管起来
创建型模式的核心就一个:封装对象的创建过程。说白了,就是不让调用方直接 new,而是通过某种机制去获取实例。
我个人习惯把创建型模式分成两类:
- 单例、工厂方法、抽象工厂:关注“如何创建单个或一族对象”。
- 建造者、原型:关注“如何创建复杂对象或克隆对象”。
举个例子。我在项目中遇到过这样一个场景:系统需要对接多个第三方支付渠道,每个渠道的初始化参数、签名算法都不一样。如果每次都在业务代码里 new,那一旦新增渠道,就得改一堆地方。后来我用抽象工厂模式,把每个渠道的支付对象创建逻辑封装到对应的工厂里。新增渠道时,只需要加一个工厂类,业务代码完全不用动。
避坑指南:我曾经在单例模式上栽过跟头——在分布式环境下用了简单的双重检查锁,结果因为指令重排序导致获取到未完全初始化的实例。后来加了 volatile 才解决。记住:单例在分布式场景下要考虑序列化、反射攻击和集群唯一性。
| 模式 | 核心意图 | 典型场景 |
|---|---|---|
| 单例 | 确保一个类只有一个实例 | 配置管理器、连接池、日志记录器 |
| 工厂方法 | 定义一个创建对象的接口,让子类决定实例化哪个类 | 日志框架(不同日志级别创建不同处理器) |
| 抽象工厂 | 创建一组相关或依赖的对象 | 跨平台UI组件(Windows按钮、Mac按钮) |
| 建造者 | 分步骤构建复杂对象 | SQL查询构建器、复杂订单对象 |
| 原型 | 通过克隆创建对象 | 对象拷贝、避免重复初始化开销 |
二、结构型模式:把类和对象“拼”出花来
结构型模式解决的是“如何组合已有的类或对象,让它们协同工作”。你想想看,很多时候我们不是缺功能,而是不知道怎么把现有的模块优雅地拼起来。
这里我重点说三个最常用的:
- 适配器:让接口不兼容的类能一起工作。说白了就是“转接头”。
- 装饰器:动态给对象添加新功能,比继承更灵活。
- 代理:控制对对象的访问,可以做延迟加载、权限校验等。
我记得有一次做旧系统改造,老代码里有一个日志类,只支持写文件。新需求要求同时写数据库。如果直接改老代码,风险太大。我用了适配器模式,写了一个适配器,把老日志类的接口转成新接口,再注入到新系统里。整个过程没动一行老代码。
注意:装饰器模式容易和继承混淆。我的经验是:如果功能扩展点很多且可能组合,用装饰器;如果扩展点固定且层次清晰,用继承。我曾经在一个项目中用装饰器给订单对象加各种折扣计算,结果嵌套了五六层,调试时根本看不清调用链。后来改用策略模式+组合,反而更清晰。
| 模式 | 核心意图 | 典型场景 |
|---|---|---|
| 适配器 | 让接口不兼容的类协同工作 | 老系统接口适配、第三方SDK封装 |
| 桥接 | 将抽象部分与实现部分分离 | 跨平台图形绘制(形状+颜色) |
| 组合 | 将对象组合成树形结构表示“部分-整体” | 文件系统、组织架构树 |
| 装饰器 | 动态给对象添加职责 | IO流(BufferedInputStream)、权限校验 |
| 外观 | 为子系统提供统一的高层接口 | 复杂库的简化入口(如SLF4J) |
| 享元 | 共享细粒度对象,减少内存占用 | 字符串常量池、游戏中的子弹对象 |
| 代理 | 控制对对象的访问 | 延迟加载、远程代理、权限控制 |
三、行为型模式:把对象之间的“互动”管好
行为型模式是最难掌握的一类,因为它涉及对象之间的通信、职责分配和算法封装。说白了,就是解决“谁该做什么,怎么做,以及怎么通知别人”。
我个人觉得,行为型模式里最实用的是这三个:
- 策略模式:把算法封装成独立的策略类,运行时可以互相替换。
- 观察者模式:定义一对多依赖,一个对象状态变化,所有依赖者自动收到通知。
- 模板方法模式:在父类定义算法骨架,子类实现具体步骤。
嗯,这里要特别说一下策略模式。我在做支付系统时,不同支付渠道的优惠计算逻辑完全不同。如果用 if-else,那代码会膨胀到没法维护。我用策略模式,把每种优惠算法封装成一个策略类,然后通过工厂获取。新增一种优惠时,只需要加一个策略类,再注册到工厂里就行。
一个容易混淆的点:策略模式和状态模式看起来很像,但意图不同。策略模式是“算法可替换”,客户端主动选择策略;状态模式是“状态驱动行为变化”,状态自动切换。我曾经在订单状态流转中用错了策略模式,结果状态切换逻辑散落在各个策略类里,后来改成状态模式才理顺。
| 模式 | 核心意图 | 典型场景 |
|---|---|---|
| 策略 | 定义一系列算法,封装起来,使它们可互换 | 支付优惠计算、排序算法选择 |
| 观察者 | 定义一对多依赖,状态变化时通知所有观察者 | 事件监听、消息推送、MVC架构 |
| 模板方法 | 在父类定义算法骨架,子类实现具体步骤 | JdbcTemplate、工作流引擎 |
| 命令 | 将请求封装为对象,支持撤销、队列、日志 | 操作撤销、任务队列、宏录制 |
| 迭代器 | 提供一种顺序访问聚合对象元素的方法 | 集合遍历、数据库游标 |
| 中介者 | 用一个中介对象封装一组对象的交互 | 聊天室、GUI组件协调 |
| 备忘录 | 在不破坏封装的前提下,保存和恢复对象状态 | 游戏存档、编辑器撤销 |
| 解释器 | 为语言定义文法表示,并解释句子 | 正则表达式引擎、SQL解析 |
| 职责链 | 将请求沿着处理链传递,直到有对象处理它 | 审批流程、过滤器链 |
| 访问者 | 在不改变元素类的前提下,为元素增加新操作 | 编译器语法树遍历、报表生成 |
四、三类模式的核心区别:一张图看懂
说了这么多,咱们用一张图把三类模式的核心区别总结一下。这张图我画了很多遍,每次给团队培训都用它。
五、如何选择?我的实战经验
最后,分享几个我实际选型时的判断标准:
- 先看问题本质:是“创建对象麻烦”还是“组合对象麻烦”还是“对象协作麻烦”?对应到三类模式。
- 再看变化点:如果变化的是“算法”,用策略;如果变化的是“对象创建方式”,用工厂;如果变化的是“对象行为”,用状态或观察者。
- 不要为了用模式而用模式:我见过有人在一个只有三个类的项目里用了抽象工厂+单例+观察者,结果代码比不用模式还复杂。模式是工具,不是目的。
- 优先用组合,少用继承:结构型模式里,装饰器、适配器都是组合思想的体现。组合比继承更灵活,更容易应对变化。
一个小技巧:当你觉得代码里 if-else 太多,或者新增一个功能要改很多类时,大概率是设计模式没用好。这时候停下来,画一下类图,看看能不能用某个模式重构。我每次重构前都会问自己:“这个变化点,对应哪个模式的意图?”
好了,关于三类模式的总结就到这里。记住:模式是经验的结晶,不是教条。多在实践中体会,慢慢你就能形成自己的“模式直觉”。