设计模式(GoF,1994)是 23 个「面向对象场景的常见解法」的命名与整理。它名声的两极:入门者把它当咒语集(每个类都想套个模式),老手把它当「重构的目标形态」(先有烂代码,模式是重构出来的结果)。本章讲三种类型的代表模式 + 每个模式的「解决什么问题、什么时候别用」,以及把 23 个模式串起来的两条底层原则。Java 课第 8 章(接口、多态)是本章的语法基础。
开闭原则(OCP):对扩展开放,对修改关闭。新功能靠「加新类」而不是「改老代码里的 if」。改老代码意味着重新测试所有依赖它的路径;加新代码风险面小。
依赖倒置原则(DIP):高层模块不该依赖低层模块,两者都依赖抽象(接口)。业务逻辑依赖「存储接口」而不是 MySQL 类,换数据库不改业务;测试时注入假存储(第 8 章的 mock)。
还有单一职责(SRP,一个类只有一个变化的理由)和里氏替换(LSP,子类必须能安全地当父类用)。这四条原则(SOLID 的主干)是所有模式的「为什么」。模式 = 原则的常见落地姿势,先懂原则,模式就是「哦,这样落」,而不是死记 UML 图。
单例(Singleton):全局只需一个的对象(配置中心、连接池)。Java 实现:私有构造 + 静态方法返回唯一实例。注意事项比模式本身多:并发下的懒加载(双重检查锁)、以及第 7 章的警告「单例 = 全局可变状态的温床,能不用少用」。
工厂方法(Factory Method):把「new 什么类」集中到一个工厂函数,调用方只见接口:
interface Storage { void save(String s); }
class FileStorage implements Storage { ... }
class DbStorage implements Storage { ... }
static Storage createStorage(String env) {
return "prod".equals(env) ? new DbStorage() : new FileStorage();
}
调用方从此不 import 具体类(依赖倒置的落地),换实现改一处。它解决的问题真实且高频:构造逻辑复杂、实现可切换。
建造者(Builder):多参数对象的构造(十几个字段的可选参数),链式 setter 代替千参构造器,可读性和默认值管理双赢。
装饰器(Decorator):不改原类,包一层加功能。Java 的 IO 流(第 12 章)是教科书级实现:new BufferedReader(new FileReader(f)) 一层套一层,每层加一点能力(缓冲),组合爆炸被优雅化解。
代理(Proxy):与装饰器同形,但意图不同:控制访问(延迟加载、权限检查、远程调用桩)。MyBatis 的 Mapper、RPC 客户端的桩都是代理。区分装饰器与代理看意图:加功能 vs 管访问。
适配器(Adapter):把不兼容的旧接口包成新接口。真实世界的刚需:引入的第三方库接口和你的内部接口不一致,包一层,业务代码零改动。技术债的常用药。
观察者(Observer):一对多的依赖,被观察者状态变化时自动通知所有观察者。事件系统(Java 课第 11 章 GUI 的 listener)、消息队列、前端框架的响应式(第 8 章 Vue 的依赖收集),全是观察者或它的变体。它是「解耦通知」的标准答案:被观察者不知道观察者是谁。
策略(Strategy):一组可互换的算法,调用方注入其一。排序的比较器(Java 课第 11 章 qsort 的 cmp 回调,计组课第 11 章函数指针)就是策略;支付方式选择(微信/支付宝/银行卡)是业务版。它消灭的是「方法里 300 行 if-else」的怪物。
模板方法(Template Method):父类定流程骨架,子类填步骤。抽象类的经典用途(Java 课第 8 章 Shape.describe() 调抽象 area()):「流程固定、步骤可变」的场景。现代替代品是「组合优于继承」:把可变步骤做成函数参数传入,减少继承层级。
三种真实的滥用病:为了模式而模式(三层类结构包一个 if,半年后没人看得懂);简历驱动设计(微服务里硬塞 18 个模式);以及过早抽象(只有一个实现就抽接口+工厂,YAGNI 原则:You Aren't Gonna Need It)。模式的上手正确姿势:从重构出发——先有重复代码/巨石条件语句(第 7 章的坏味道),再识别「这像某个模式解决的痛」,重构到位。模式是重构的目的地,不是起点。
new FileInputStream(Java 课第 12 章),需求变更要求统一加上重试逻辑。对比「逐处修改」和「引入工厂 + 装饰器」的改动点数量,写一段选择论证。