1. 设计模式到底是什么从“背模式”到“懂结构”的认知起点1.1 一个反直觉的观察先讲个我观察了很久的现象。很多开发者学设计模式抱着《Head First 设计模式》或者 GoF 原书啃了两三遍23 种模式的名字、UML 图、优缺点背得滚瓜烂熟一上机写代码却完全不知道怎么用。面试问到“策略模式和状态模式什么区别”能答上来回到工位面对一团 spaghetti 代码脑子里那 23 张图全成了摆设。问题出在哪大多数人把设计模式当成了“知识”——记住它、复述它、考完试就忘。但设计模式本质上是“结构”——它描述的是一组类和方法之间的组织方式是应对软件变化的一种折中方案。我自己的认知转折发生在做了几年项目、重构过好几轮老旧系统之后。某天突然发现我压根不需要刻意去想“这里该用哪个模式”而是代码的自然演进过程把我推向了某个模式。比如一个方法里长出了五六种 if 分支每种分支都要执行一段不同的算法我下意识就想把它拆成一个接口加多个实现——这就是策略模式的雏形。再比如一个对象的状态在运行期频繁切换、不同状态下同一方法的行为完全不同我自然就会引入状态对象——这是状态模式。所以这篇东西我换一种讲法不按 GoF 原书的顺序念 23 张模式卡片。我会先带你建立一张宏观地图把 23 种模式按“它们到底在解决什么问题”重新归类再逐个拆解但重心放在最容易混淆、最常被问到的那些模式上最后用对比表和实践经验告诉你真实项目里怎么选、怎么落地。1.2 三个分类到底在解决哪三类问题GoF 把 23 种模式分成创建型Creational、结构型Structural、行为型Behavioral三大类。很多人记不住这五个字背后的含义我用一句话帮你立住框架创建型解决的是“对象怎么来”的问题——谁负责创建它、在什么时候创建、怎么创建才能不让调用方和具体类耦合。结构型解决的是“对象和对象之间怎么拼”的问题——多个类如何组合成一个更大的结构同时保持各部分的独立性。行为型解决的是“对象之间怎么协作”的问题——职责怎么分配、算法怎么封装、对象之间怎么通信。一句话版本创建型管出生结构型管组织行为型管协作。这个分类不是随便分的。GoF 时期的面向对象设计基本矛盾是“复用”和“变化”的冲突你要复用一段逻辑最简单的方式是继承但继承是静态绑定父类一改动子类全受影响你要应对变化最简单的方式是分支判断但分支一多代码就腐烂。三类模式的共同目标是同一个——封装变化只是封装的维度不同。我习惯给这些模式贴几个“变化维度”的标签分类封装的“变化”核心思路创建型对象的具体类、创建流程和生命周期让创建逻辑与使用逻辑解耦调用方只面向抽象接口结构型对象之间的组合关系和接口形态用组合/聚合/适配来替代硬编码的继承关系行为型对象之间的交互方式、算法的实现细节把会变的行为和稳定的骨架分离有了这张地图你看任何一份代码先问三个问题对象是谁创建出来的创建方式会不会随着需求变化对象之间的依赖是写死的还是可替换的一旦这些问题在你脑子里自动浮现你就已经比“背完 23 种模式”的人领先一大截了。2. 创建型 5 种对象由谁负责、如何诞生、什么时候诞生2.1 工厂三兄弟——把“new”这层皮剥掉创建型模式一共有 5 个简单工厂虽然不算是 GoF 定义的正式模式但它是理解工厂家族的入口、工厂方法、抽象工厂、单例、原型、建造者。是的6 个多出来的那个是简单工厂。标题写的是 23 种但讲创建型的时候几乎绕不开简单工厂所以它通常是“231”的讨论对象。先看最直觉的问题为什么我们要费劲去搞工厂最直接的原因new是面向对象代码里最隐蔽的耦合源。你写ListString list new ArrayList()这里list的类型其实已经泄露了——你本来只需要一个“能按顺序存元素的容器”但你硬生生绑定了具体实现。如果哪天你想换成LinkedList就得跑去所有创建它的地方改。对象一多、调用方一多这种改动就是一场灾难。工厂方法模式Factory Method的思路是把“创建某个类的实例”这个动作抽象成方法让子类决定实例化哪一个具体类。它的结构很轻abstract class Product { abstract void use(); } class ConcreteProductA extends Product { void use() { System.out.println(Product A); } } abstract class Creator { abstract Product createProduct(); void someOperation() { Product p createProduct(); p.use(); } } class ConcreteCreatorA extends Creator { Product createProduct() { return new ConcreteProductA(); } }你看客户端代码只依赖Creator和Product两个抽象至于createProduct()返回的到底是 A 还是 B子类说了算。工厂方法的核心价值在于它能让你在一个稳定流程里插入变化的创建逻辑。比如框架里的load()流程你需要框架帮你处理加载、缓存、日志但具体加载什么格式的数据子类自己决定——这就是模板方法加工厂方法组合后面讲模板方法时再呼应。抽象工厂Abstract Factory比工厂方法更进一步它管理的是“一族相关的产品”。典型场景是 UI 框架你要创建一套按钮、输入框、弹窗它们在 Windows 风格下是一套实现在 Mac 风格下是另一套实现。你不可能让一个工厂方法同时管按钮和输入框因为你在createButton()里拿到的是按钮在createDialog()里拿到的是对话框它们之间也许需要匹配同一个主题。所以抽象工厂的形态是多个工厂方法合在一起interface GuiFactory { Button createButton(); Dialog createDialog(); } class WindowsFactory implements GuiFactory { public Button createButton() { return new WindowsButton(); } public Dialog createDialog() { return new WindowsDialog(); } }简单工厂Simple Factory不是模式书的主角但它在实践中非常常用。它只是一个提供静态创建方法的类根据参数决定返回哪种实现public class ReportFactory { public static Report create(String type) { if (pdf.equals(type)) return new PdfReport(); if (excel.equals(type)) return new ExcelReport(); throw new IllegalArgumentException(unknown report type); } }简单工厂容易被人诟病“违反开闭原则”因为每加一种新报表都要改这个类。但它胜在直观适合创建逻辑简单、扩展频率低的场景。我的态度是别教条工具要服务于项目实际情况。2.2 单例与原型——生命周期与复制单例模式Singleton应该是大家最早接触、也是被滥用最严重的模式之一。它的目标是保证一个类只有一个实例并提供全局访问点。经典写法public class ConfigManager { private static volatile ConfigManager instance; private Properties config; private ConfigManager() { config loadFromFile(); } public static ConfigManager getInstance() { if (instance null) { synchronized (ConfigManager.class) { if (instance null) { instance new ConfigManager(); } } } return instance; } }双检锁Double-Checked Locking在 Java 5 之后配合volatile是安全的。但我要提醒一句单例模式真正的坑不在写法而在滥用。很多人把全局变量换了个马甲叫单例然后在里面塞入各种可变状态。多个模块共享一个可变单例调试起来极其痛苦——你根本不知道是谁改了这个状态。我自己的实践原则是单例只用于“无状态服务”或“只读配置”有可变状态就考虑用事件驱动或显式传参。另外现代的依赖注入容器Spring 等默认就是单例管理你就不需要手动写getInstance()了把对象交给容器管理控制反转比自造单例干净得多。原型模式Prototype解决的问题是创建对象成本很高比如需要查库、需要复杂计算而复制已有对象比重新创建便宜得多。Java 里的Cloneable接口就是标准的原型原型但这里有个著名的坑——浅拷贝和深拷贝。拿一个包含ListItem字段的对象举例默认的clone()是浅拷贝新对象的items还是指向同一个列表对象。你往列表里加一条原对象也变了。要实现真正的深拷贝你得在clone()里手动复制内部集合Override public Order clone() { Order o (Order) super.clone(); o.items new ArrayList(this.items); return o; }原型模式在实际业务中通常不如工厂和单例常用但它在你写克隆、快照、配置复制一类的功能时很有用。思路是把“复制”这个动作本身封装起来调用方不需要知道对象内部结构只需要说一句“给我来一份一样的”。2.3 创建型剩余模式速查表建造者模式Builder值得单独提一句。它解决的是“构造参数太多、构造过程需要分步”的问题。典型场景一个对象有十几个可选参数用全参构造函数会导致调用方完全看不懂Pizza pizza new PizzaBuilder() .size(large) .addCheese() .addPepperoni() .build();每个addXxx()方法返回this最后build()返回完整对象。这种方式提升了可读性也保证了对象在构建过程中的一致性。很多框架里的Builder也是这么实现的。建造者和工厂、抽象工厂容易混淆我在这里先做个区分后面还会细说模式特征类比工厂方法一个创建方法与一个产品对应子类决定实现到固定窗口取一样的套餐抽象工厂一个工厂负责一族产品产品之间有关联一家品牌门店里所有同风格商品建造者分步骤构建对象重点在构建流程和可读性点菜时逐步定制最后一步出锅原型复制已有对象而非重新 new打印时复印一张原件创建型的核心就一句话别让new这个动作散落在你的业务逻辑里。把创建逻辑收拢到一个变化点业务代码才干净。3. 结构型 7 种对象与对象之间怎么拼装成可维护的系统3.1 适配器、装饰器、代理——最容易混淆的三兄弟结构型模式有 7 个适配器Adapter、桥接Bridge、组合Composite、装饰器Decorator、外观Facade、享元Flyweight、代理Proxy。刚开始学设计模式的人十有八九会在适配器、装饰器、代理之间犯迷糊——这三者表面上都是在“包装”另一个对象但包装的目的完全不同。适配器Adapter要解决的是“接口不匹配”的问题。你有一个老接口一个需要新接口的客户端两个接口语义相近但方法名对不上你又不想改老接口于是写一层转换壳。// 旧接口 interface OldMessageSender { void send(String text); } // 新客户端期望的接口 interface MessageSender { void sendMessage(String text, String channel); } class OldMessageSenderAdapter implements MessageSender { private OldMessageSender oldSender; public MessageSenderAdapter(OldMessageSender oldSender) { this.oldSender oldSender; } public void sendMessage(String text, String channel) { // 把新接口的调用转换成旧接口的调用 oldSender.send([ channel ] text); } }适配器的关键词是“翻译”和“兼容”它本身不增加行为只是换了个姿势暴露原有功能。装饰器Decorator想解决的问题是“给对象动态添加职责”。你不用在基类里堆一堆可选的属性和方法也不用为每种组合写一个子类而是用一层一层的包装来增强能力。interface Coffee { double cost(); String desc(); } class Espresso implements Coffee { public double cost() { return 5.0; } public String desc() { return 浓缩咖啡; } } class MilkDecorator implements Coffee { private Coffee coffee; public MilkDecorator(Coffee coffee) { this.coffee coffee; } public double cost() { return coffee.cost() 2.0; } public String desc() { return coffee.desc() 加奶; } } Coffee c new MilkDecorator(new Espresso());装饰器的关键词是“增强”和“叠加”。它和继承最大的区别是动态性运行期想加几层就加几层克制了子类爆炸的问题。Java IO 的BufferedInputStream、DataInputStream一层套一层就是装饰器的教科书级实践。代理Proxy伪装成目标对象的替身但目的不是增强功能而是控制访问。最经典的场景是延迟加载——一个重量级对象只有真正需要使用时才初始化由代理对象暂时代替它占位class LazyImageProxy implements Image { private RealImage realImage; private String filename; public LazyImageProxy(String filename) { this.filename filename; } public void show() { if (realImage null) { realImage new RealImage(filename); } realImage.show(); } }代理的关键词是“控制”和“保护”。远程代理RPC 里的 stub、虚拟代理延迟加载、安全代理鉴权都属于这个范畴。Spring AOP 的动态代理也是代理模式在框架层面的运用——你在方法执行前后插入逻辑调用方却以为自己调的是目标对象本身。一句话总结三兄弟适配器是“对方想听 A 你只会说 B配个翻译”装饰器是“在原有东西上叠 buff”代理是“让人把不重要的事先放一放碰到正主才开工”。3.2 外观、组合、桥接、享元——从“抽象层级”看结构外观模式Facade是我认为性价比最高的结构型模式没有之一。它的思想特别朴素给一个复杂子系统提供一个统一的简单入口客户端不需要知道子系统内部有 5 个类互相调用。以OrderService为例下单过程涉及库存检查、支付扣款、发送通知多个模块。直接让业务方挨个调一遍业务方耦合了一堆细节而且顺序错了就出问题。封装一个placeOrder()方法内部编排所有流程这就是外观模式。外观模式和适配器的区别适配器重点在“接口形态转换”外观模式重点在“提供一个简化视图”。组合模式Composite解决的是“部分-整体”的树形结构问题。文件系统、组织架构、菜单系统都是天然树形结构组合模式让叶子节点和容器节点使用同一套接口客户端不用区分“这是一个文件夹还是一个文件”统一调用。interface FileNode { void display(); } class File implements FileNode { public void display() { System.out.println(file); } } class Folder implements FileNode { private ListFileNode children new ArrayList(); public void add(FileNode node) { children.add(node); } public void display() { for (FileNode child : children) child.display(); } }组合模式的核心价值让树上的节点一致对待调用方少一堆 if。桥接模式Bridge解决的问题是“多维度的变化组合”。比如你有一个消息类型短信、邮件和一个发送渠道加急、普通两个维度都在变化。如果用继承短信加急、短信普通、邮件加急……类数量爆炸。桥接模式把抽象部分和实现部分分离各自独立变化interface MessageSender { void send(String message); } class EmailSender implements MessageSender { public void send(String msg) { System.out.println(mail: msg); } } abstract class Message { protected MessageSender sender; public Message(MessageSender sender) { this.sender sender; } abstract void send(String msg); } class UrgentMessage extends Message { public UrgentMessage(MessageSender sender) { super(sender); } public void send(String msg) { sender.send([urgent] msg); } }这里Message和MessageSender各自独立变化通过组合关联在一起。理解桥接模式的关键是把继承树变成组合用“有一个”代替“是一个”。享元模式Flyweight稍微冷门一些但它在性能和复用方面很有价值。核心思想是共享对象减少创建数量。适合“大量相似对象 内部状态可分离”的场景。比如一款游戏渲染大量同种树木每棵树的位置、大小不同但纹理、形状相同。共享纹理只保留每棵树的位置差异数据内存立刻就降下来了。在 Java 里Integer.valueOf()对-128 ~ 127范围内的对象做了缓存就是享元模式的典型应用。你写Integer a 100; Integer b 100;a b为true换成 200 就不一样了——因为这个范围之外会 new 新对象。3.3 结构型模式的使用场景对照表模式核心问题典型场景一句话记忆适配器接口不匹配对接老系统、第三方 SDK翻译官桥接多维变化导致类爆炸消息类型 发送渠道变化分离组合树形结构处理文件系统、菜单、组织架构树枝树叶一样对待装饰器动态增加职责Java IO、加权限/日志叠 buff外观子系统过于复杂下单、支付等门面服务前台接待享元大量相似对象缓存、池化共享复用代理控制对目标对象的访问延迟加载、AOP、鉴权替身结构型模式的核心思路是用组合替代继承。GoF 全书反复强调一个原则优先组合而不是继承。继承是静态的、白盒的父类一改子类全受影响组合是动态的、黑盒的你可以运行期替换一个对象的行为。这 7 个模式里有 5 个本质上都是在用“组合”来解耦。4. 行为型 11 种对象之间怎么协作、怎么通信4.1 策略与状态——看似双胞胎实则分身术行为型模式一共 11 个是三类里数量最多的也是最容易让人头大的部分。我建议从最常用的两个入手策略Strategy和状态State。策略模式Strategy解决的是“算法族”的封装问题。一个方法里 if-else 开了花每种分支对应一种算法你要做的是把算法各自封装成独立的类客户端运行时根据自己的需求选择。interface DiscountStrategy { double calculate(double price); } class StudentDiscount implements DiscountStrategy { public double calculate(double price) { return price * 0.8; } } class VipDiscount implements DiscountStrategy { public double calculate(double price) { return price * 0.7; } } class PriceCalculator { private DiscountStrategy strategy; public PriceCalculator(DiscountStrategy strategy) { this.strategy strategy; } public double calc(double p) { return strategy.calculate(p); } }比 if-else 强在哪第一新增策略不用改原有代码开闭原则第二每个策略的测试可以独立进行第三调用方和具体策略解耦。状态模式State和策略模式的代码结构长得很像但意图完全不同。状态模式解决的是“对象在不同状态下同一方法有不同的行为”。订单就是典型例子待支付、已支付、已发货、已签收。每个状态下调用cancel()的语义都不一样。待支付时取消订单可以直接取消已发货时取消可能是申请退货。你要是在订单类里写一堆 if 判断状态代码会随着状态增多迅速腐烂。状态模式的做法把状态本身抽象成对象订单类持有一个当前状态对象行为委托给状态对象interface OrderState { void pay(Order order); void cancel(Order order); } class PendingState implements OrderState { public void pay(Order order) { System.out.println(支付成功); order.setState(new PaidState()); } public void cancel(Order order) { System.out.println(直接取消); order.setState(new CancelledState()); } } class Order { private OrderState state new PendingState(); public void setState(OrderState state) { this.state state; } public void pay() { state.pay(this); } public void cancel() { state.cancel(this); } }很多初学者问策略和状态看着一样啊怎么区分我的理解是策略模式中策略对象是被客户端主动选择的算法之间没有“自动流转”的关系状态模式中状态对象是根据业务逻辑自动切换的状态转移是模式本身的一部分。策略是“客户挑工具”状态是“系统自动变换工作模式”。4.2 观察者与责任链——事件驱动的两种骨架观察者模式Observer是 GUI 编程、Spring 的事件机制、消息推送系统的地基。它的核心是一对多的依赖关系被观察者状态变化时自动通知所有注册的观察者。Java 里最经典的实现是java.util.Observer接口但真实项目里我们更常自己定义一个轻量版本interface EventListener { void onEvent(String eventType, String data); } class EventManager { private MapString, ListEventListener listeners new HashMap(); public void subscribe(String eventType, EventListener listener) { listeners.computeIfAbsent(eventType, k - new ArrayList()).add(listener); } public void publish(String eventType, String data) { ListEventListener list listeners.get(eventType); if (list ! null) { for (EventListener l : list) l.onEvent(eventType, data); } } }观察者模式最大的价值是发布方和订阅方彻底解耦。订单服务不需要知道短信服务、邮件服务、库存服务的存在只是往事件里发一条消息谁订阅谁处理。这也让后续的系统扩展变得非常自然——新增一个营销服务只需订阅相应事件不需要改老代码。责任链模式Chain of Responsibility解决的是“多个处理器按顺序尝试处理请求”的问题。业务里最典型的场景是审批流、消息过滤器、异常处理链。abstract class Approver { protected Approver next; public void setNext(Approver next) { this.next next; } public abstract void approve(double amount); } class Manager extends Approver { public void approve(double amount) { if (amount 1000) System.out.println(经理审批通过); else if (next ! null) next.approve(amount); } } class Director extends Approver { public void approve(double amount) { if (amount 10000) System.out.println(总监审批通过); else if (next ! null) next.approve(amount); } }责任链的好处处理者和请求者解耦链条的结构可以灵活调整——加一个环节不影响前面的环节。坏处也有如果链条太长或者链路里有环形引用容易出问题性能也受影响。所以责任链适合处理环节固定、每条链路长度可控的场景。4.3 模板方法、命令、迭代器——套路化流程的收编模板方法模式Template Method是我个人最推荐优先掌握的模式因为它极其直观并且实用。思路是父类定义一个算法的骨架把某些步骤延迟到子类实现。以数据导出为例导出流程固定查数据 - 格式化 - 写文件。但不同格式的格式化逻辑不同。模板方法把稳定流程写在父类把变化点交给子类abstract class DataExporter { public final void export() { ListData data fetchData(); String content format(data); write(content); } protected abstract String format(ListData data); private ListData fetchData() { /* 固定逻辑 */ } private void write(String content) { /* 固定逻辑 */ } } class JsonExporter extends DataExporter { protected String format(ListData data) { /* JSON 格式化 */ } }模板方法看起来像是“高级版的继承复用”但它真正优雅的地方在于框架写好了扩展的人只需要填缝。很多框架的核心流程都是模板方法模式——Spring 的JdbcTemplate、Servlet 的service()方法都大量使用这个思想。模板方法和策略模式容易混淆后面我专门做对比。命令模式Command把“请求”封装成对象从而支持参数化传递、队列化、撤销操作。把操作本身当成参数传来传去这在很多框架里都有体现。interface Command { void execute(); void undo(); } class LightOnCommand implements Command { private Light light; public void execute() { light.on(); } public void undo() { light.off(); } }经典场景是编辑器的撤销重做、任务队列、事务操作。把“做什么”和“谁来做”分离任务发起方只需要拥有一个命令对象不关心命令内部调用了哪些类。迭代器模式Iterator可能是大家实际用得最多、却不太会刻意想起的模式——因为现代语言已经把它内建了。for (String s : list)背后的Iterator就是迭代器模式。它的核心价值是提供统一方法遍历集合而不暴露集合内部结构。如果要自己写核心就是两个接口一个hasNext()一个next()。现代语言里很少需要自己实现理解它重点在于集合的内部表现和遍历逻辑分离所以你可以为同一份数据提供不同的遍历方式正序、倒序、过滤等。4.4 剩余行为模式中介者、备忘录、解释器、访问者这四个模式在业务代码里相对低频但很值得了解因为它们出现在特定场景中的概率其实不低。中介者模式Mediator解决的是“多个对象之间网状交互”的问题。多个对象互相直接持有引用耦合就重了引入一个中介中心让对象与对象之间的交互变成对象与中介的交互。聊天室就是标准场景每个用户往聊天室发消息聊天室负责转发给所有人。Spring MVC 里的DispatcherServlet也有中介者的影子——所有请求先到它它再分发给对应的 Controller。备忘录模式Memento是给对象提供“后悔药”的机制用于保存和恢复对象的内部状态但又不破坏封装性。编辑器里的撤销、游戏里的存档、页面的表单恢复都是这个模式。核心是三个角色发起人Originator、备忘录Memento、负责管理备忘的 Caretaker。备忘录对象应该对外部隐藏状态细节否则就泄露了发起人内部信息。解释器模式Interpreter定义语法的表示并解释执行特定语言。听起来非常高端但真实业务中应用场景并不多——通常只在表达式计算器、SQL 解析、规则引擎、SimpleDateFormat模式串等需要自定义语法的场景出现。我见过最实用的案例是用它实现一套简单的规则引擎把“age 18 and vip true”这样的表达式解析成树形结构并求值。缺点是语法规则一复杂类树会非常庞大不建议在复杂语言场景硬上。访问者模式Visitor在不修改已有类的前提下为对象结构增加新的操作。它适合对象结构相对稳定、但操作频繁扩展的场景。典型例子是编译器的语法树——节点种类固定表达式、赋值等但你需要对树执行各种操作类型检查、代码生成、格式化、统计。访问者模式把“操作”和“数据结构”分离让新增操作变得容易。但访问者模式有个不可忽视的副作用它会把操作逻辑分散在多个 Visitor 类中而且一旦数据结构发生变更所有 Visitor 都要跟着改。业务代码里如果对象结构本身不稳定我不建议硬套访问者模式反而 Java 21 的 pattern matching 或instanceof模式匹配在某些场景下更灵活。4.5 行为型模式一览表模式核心问题典型场景一句话记忆策略算法族变化优惠计算、压缩算法可插拔算法模板方法流程骨架稳定步骤可变导出、构建流程搭好架子填缝观察者一对多通知事件总线、消息推送发布订阅迭代器统一遍历接口集合遍历统一走一遍责任链多级处理审批流、过滤器接力棒命令请求封装、撤销编辑撤销、任务队列把操作打包状态状态切换与行为联动订单、流程引擎状态机引擎中介者网状改星状聊天室、MVC中间人备忘录快照与恢复存档、撤销后悔药解释器自定义语法执行规则引擎、表达式解析写个小语言访问者在稳定结构上扩展操作编译期、报表参观团5. 模式对比与选择到底哪些模式最容易拿错5.1 工厂三兄弟到底怎么区分很多读者整篇读完最纠结的还是工厂三兄弟。我用一张对比表和一段话把它钉死维度简单工厂工厂方法抽象工厂粒度一个类一个静态方法一个创建方法子类覆写一组创建方法制造一族产品灵活性低加类型要改工厂类中加类型要加子类高加产品族加具体工厂调用方式传参数返回产品子类返回产品整个工厂启动返回一族产品适用场景产品类型少、扩展少单一产品、重流程复用产品族之间有约束一句话简单工厂是“大门旁边的小窗口”你点菜、它做菜工厂方法是“子类自己带菜单”抽象工厂是“一整套主题套餐碗筷刀叉都是一个风格的”。5.2 装饰器 vs 代理 vs 适配器这也是面试高频题。我把它们放在同一张表里模式意图代码特征例适配器接口转换包装对象并把方法重命名/重组老接口适配新接口装饰器动态增强多层包每层实现相同接口IO 流套 Buffered代理访问控制与目标类实现同一接口控制调用时机延迟加载、AOP从代码结构上讲装饰器和代理都“包住”了被代理对象但装饰器关注“增加能力”代理关注“替你把关”。适配器则是“换个说法讲同一件事”根本目的不同。实际使用中还有一个判断小技巧如果你写代码时脑子里想的是“我要给这个方法加点日志/缓存”那是装饰器如果你想的是“这个对象很重先别创建等用到时候再说”那是代理如果你接手的第三方接口方法名和你系统里不匹配那是适配器。5.3 策略 vs 状态 vs 模板方法这三个模式曾经把我绕晕过很长一段时间现在我用一张表彻底讲清模式谁会变变化如何发生记忆锚点策略算法整体客户端指定运行时一次性注入换挡状态对象在不同状态的行为状态对象内部自动驱动转移换模式模板方法流程中某几个步骤子类覆写流程顺序由父类固定换步骤策略和模板方法最大的区别策略把“整个算法”作为可替换的整体模板方法把“算法的一部分步骤”作为可变点。策略模式里调用方主动挑一个策略模板方法里调用方用的是父类同一个流程只是流程中的某一步被子类实现覆盖。状态和策略的形式最像但状态模式的内涵更接近于“有限状态机”状态对象持有“转移路径”的信息当前状态处理完事件后会设置下一个状态策略模式没有“状态转移”的概念策略用完就完了不会自动换到另一个策略。5.4 设计模式选择决策链路我在真实项目里遇到“不知道选哪个模式”的情况一般按下面这套链路走库里的“模式 check list”经常能帮我避免头脑一热乱套先问自己变化点到底是什么是对象怎么创建、还是对象之间怎么协作如果是对象创建是产品族相关还是单产品需要分步构建吗对象太贵需要复用吗如果是结构组装是接口不匹配还是能力动态叠加还是多维变化如果是行为协作是一对多通知还是算法可替换还是状态驱动还是处理链最后灵魂拷问不引入模式直接写分支会怎样如果分支可控、变数不多别为了模式而模式。引入新模式的成本包括文件数、维护者理解成本要算进去。我把这个决策路径画成文字版本方便你拍照存下来状态多变→ 状态模式。 算法可选→ 策略模式。 流程固定、步骤可变→ 模板方法。 事件扩散→ 观察者。 组合爆炸→ 桥接或装饰器。 接口不兼容→ 适配器。 对象太贵→ 单例或原型或享元。 请求要排队处理→ 责任链。 多层次通知→ 观察者或中介者。6. 如何把这 23 种模式真正落到真实项目里6.1 从你最常遇到的重构场景反推学设计模式最高的效率不是翻书背书而是带着真实问题来“检索”。我建议你把这个清单收藏下来下次遇到对应情况时直接对号入座。一个类里有大段 if-else每个分支都在做类似的差异化处理优先考虑策略模式或状态模式看是“外部选择”还是“内部流转”。每次新增业务类型都要改一堆 case/switch 分发考虑用工厂 策略组合让新类型新增一个类就完事。两个系统之间接口对不上改任意一方都会引发大范围改动适配器。流程骨架是固定的只是每个环节实现不同模板方法模式把稳定流程写成 final 方法。一个对象被多个模块监听状态一变化就要通知一堆地方观察者最好直接接事件总线。对象初始化参数过多而且大量是可选项建造者模式。有大量同结构对象内存迟迟降不下来享元 对象池。一个复杂子系统对上层暴露了太多类外观模式直接封装一个门面接口。把这些场景倒过来就是 23 种模式的应用地图。项目里第一批重构从策略、模板方法、观察者、适配器这四个下手性价比最高。我自己的经历曾经维护过一套老业务代码里面的一个核心方法 500 多行全是根据业务类型做各种分支新需求来了就在中间加一段测也不敢测改也不敢改。后来用策略模式重构成了几个独立的策略类每个类负责一种业务类型的完整处理流程。重构完新业务类型只需要新增一个类注册进去主流程代码 100 行以内搞定。那次之后团队小伙伴对设计模式的态度就从“面试题”变成了“救命稻草”。6.2 我踩过的过度设计反模式如果说有哪个话题值得单独拎出来那就是过度设计。我见过太多人学完设计模式之后写了如下代码public class UserService { private final UserFactory userFactory new UserFactory(); private final UserRepository userRepository new UserRepository(); public User getUser(Long id) { return userFactory.create(userRepository.findById(id)); } }一个简单到只有两行逻辑的方法包了工厂又包了接口看起来很高大上实际维护的时候大家只能翻着各个抽象层来回跳成本远大于收益。设计模式本质上是一种折衷。你获得了灵活性和扩展性代价是更多的类、更抽象的调用链、更高的理解成本。Spring 这种大型框架值得大量使用模式但你自己的 CRUD 业务十个方法八个是直进直出的强行上模式只能自我感动。我的原则是只有当你真实感觉到了“这段代码很难改、很难测、很难扩展”的痛点你才需要引入模式来破局。模式是因痛点而来的解药不是拿来炫技的勋章。还有一类反模式是“模式套模式”。一个几十行的类里同时用了观察者、责任链、模板方法、策略看着像代码设计大师的作品实则是维护者的噩梦。模式是单一职责的解决手段组合使用时要确保各自的职责边界清晰而不是叠加一堆互相缠绕的抽象。6.3 一套值得试的学习方法最后分享一个我觉得效果很好的学习方法从代码里反推模式。找几个你熟悉的大型开源项目——Spring、Netty、MyBatis 都行——挑几个核心类自己试着标注这些类分别用了哪些模式。不用全标只会标一半也很有收获。你会发现原来框架里到处都是工厂、模板方法、代理、观察者你对这些模式的理解就从“书上的定义”变成了“可触摸的代码事实”。试着做几件小事给自己写一个“模式 × 项目场景”的映射表下一次写代码时对照它思考。每周选一个模式顺手改造一个已有的小模块体会重构前后的区别。尝试用文字画模式结构图。画到自己能顺手画出来基本上就是真懂了。还有人让我推荐资料我不整书单就说两个方向当你对每个模式的具体定义还含混时配合实际代码去读 GoF 原书的相应章节当你已经能写出来但不确定要不要用时最好的老师是重构带来的酸痛感——被 bug 追着跑几次你就知道什么是好结构了。我个人在实际项目中的最大体会设计模式不是天花板而是下限。它保证你不会写出太烂的设计。真正的架构能力是在模式之上知道什么时候该打破规则、用什么组合能应对更复杂的业务场景。这层功夫没有捷径只能在一次一次踩坑和重构里长出来。希望这篇万字总结能帮你在学模式的路上少走几段弯路。