从大二被《设计模式》期末考逼着背类图,到后来在真实项目里对着六百行的Service类重构出六个小类,我对设计模式的态度经历了整整三个阶段的转变:先是觉得它是应付考试的八股,再是觉得它是大佬炫技的武器,最后才明白它其实就是一套“识别变化、封装变化”的思考框架。
这个学习总结不是要把23种模式再抄一遍UML类图。我想分享的是另一种学习路径——先搞懂设计模式的底层逻辑(面向对象设计原则),再顺着“对象的创建、结构的组装、行为的分配”这三条主线把模式捋顺,最后聊聊设计模式在当下多Agent编排这种新场景里的迁移应用。不管你是正在准备设计模式期末、做大作业,还是在写业务代码时感觉类越来越臃肿,这篇都应该对你有用。
1. 为什么大多数人学设计模式容易“学了就忘”
1.1 把设计模式当考点,是最大的学习误区
我见过太多人(包括当年的我自己)拿着GoF的《设计模式》从第一章看到最后一章,每个模式都能背出“意图、结构、参与者、优缺点”,UML类图画得比美术生还标准。但一回到自己的项目,看到需求照样写一个又长又臭的类,完全想不起来该用哪个模式。为什么?
因为这些人在用背历史书的方式学一门手艺。设计模式的本质不是“标准答案”,而是“应对变化的设计决策”。你不去理解每个模式在解决什么类型的变化,只是记住类与类之间的静态关系,那必然过目就忘。
打个比方:学设计模式就像学做菜。菜谱(模式)告诉你“鱼香肉丝需要肉丝、木耳、胡萝卜、泡椒”,但如果你不知道为什么要先滑肉、为什么要调鱼香汁,换个食材你就不会做了。而设计原则,就是“烹饪原理”——热锅凉油不粘锅、先炒香料头更香,掌握了这些,你可以从鱼香肉丝衍生出鱼香茄子、鱼香豆腐。
1.2 正确的学习主线:识别变化点,而不是记忆类图
我后来重构了学习思路,核心就一句话:每一个设计模式,都是为了隔离某一种变化。你不需要从模式出发去找场景,而应该从场景中的变化点出发,让模式自己浮现出来。
举个很实在的例子。你的订单系统里,运费计算规则经常变——平时按重量算,大促按满减算,有些会员用户免运费。如果你把这些规则用if-else写在OrderService里,下一次改规则就要改核心业务类,风险极高。这时候你需要的其实是策略模式:把运费计算抽象成一个接口,每种算法一个实现类,运行时自由切换。你看,不是“我想用策略模式”,而是“运费规则在变,我需要把变化封装起来”,策略模式自然就出来了。
我整理了一个简易的“变化点-模式”对照表,这也是我学习时最重要的索引:
| 变化点 | 对应设计模式(部分) |
|---|---|
| 对象的创建方式/类型经常变 | 工厂方法、抽象工厂、建造者 |
| 对象的创建成本高、需要全局唯一 | 单例 |
| 接口不兼容,需要适配 | 适配器 |
| 需要在不改原类的情况下增强功能 | 装饰器 |
| 调用者不想直接操作复杂子系统 | 外观 |
| 算法或业务规则经常切换 | 策略 |
| 对象状态变化需要通知多个依赖方 | 观察者 |
| 同一请求需要多个处理者依次处理 | 责任链 |
| 对象内部状态改变导致行为改变 | 状态 |
| 算法骨架固定,但具体步骤可变 | 模板方法 |
这个表说明一件事:模式是跟着“未知变化”走的。你识别出变化点,就知道该去哪里找工具。
1.3 面向对象设计原则:模式的“根”
如果说23个模式是“招式”,那么六大设计原则就是“内功心法”。没有内功的招式是花架子,有了内功你甚至可以自创招式。
- 单一职责原则(SRP):一个类只负责一件事。这是最容易理解、也最容易被破坏的原则。一个类既要做权限校验又要做数据解析还要拼装UI,早晚崩。
- 开闭原则(OCP):对扩展开放,对修改关闭。这是设计模式的终极目标——需求变了,优先加新代码,而不是改老代码。
- 里氏替换原则(LSP):子类必须能替换父类且行为正确。继承不是“复用代码”的偷懒工具,它描述的是“is-a”关系。
- 接口隔离原则(ISP):接口要小而专,别让实现类被迫实现一堆用不上的方法。
- 依赖倒置原则(DIP):高层模块不依赖低层模块,两者都依赖抽象。翻译过来就是:业务层面向接口编程,别直接new一个具体实现。
- 最少知识原则(LoD):对象尽量少跟无关对象打交道。控制耦合范围,降低牵一发动全身的风险。
我个人建议的学习顺序是:先啃透六大原则,再用原则去推导模式的合理性。比如你理解了DIP,再看工厂模式、策略模式,会发现它们就是“依赖抽象”这一原则在创建和调用两个环节的具体落地。有了这层认知,你甚至不需要刻意背类图,模式的结构是“长”出来的,不是背出来的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 对象的诞生与管理:创建型模式的选型逻辑
2.1 从new说起:直接依赖的问题
Java程序里最常见的“坏味道”就是到处new。new本身不是错,但它带来了硬编码依赖:你在OrderService里写了new WxPay(),将来要接入支付宝,就得改动OrderService的源码,这就违反了开闭原则。
创建型模式解决的核心问题非常统一:把“对象的创建和使用”拆开。创建细节不该散落在业务代码里,而应该由一个专门的对象/方法来管理。
2.2 工厂模式:把“选择”交给谁
工厂模式常见的形态有三种,很多人分不清,我用人话讲一下:
- 简单工厂:一个静态方法,根据参数返回不同对象。严格来说它不是GoF的23种模式之一,但非常实用。缺点是每次新增产品类型,要改这个方法。
- 工厂方法:定义创建对象的接口,让子类决定实例化哪个类。把“选择”下沉到子类工厂,新增产品时不用改老代码,符合开闭原则。
- 抽象工厂:处理“一族产品”的创建。比如做跨端UI组件,Windows下的按钮、文本框是一个风格,Mac下是另一个风格,抽象工厂确保你创建出来的是风格统一的一整套产品。
以工厂方法为例,一个最小可复用的写法:
java复制public interface Vehicle {
void drive();
}
public class Car implements Vehicle {
@Override
public void drive() {
System.out.println("Car driving...");
}
}
public class Motorcycle implements Vehicle {
@Override
public void drive() {
System.out.println("Motorcycle driving...");
}
}
public interface VehicleFactory {
Vehicle create();
}
public class CarFactory implements VehicleFactory {
@Override
public Vehicle create() {
return new Car();
}
}
public class MotorcycleFactory implements VehicleFactory {
@Override
public Vehicle create() {
return new Motorcycle();
}
}
使用方只依赖Vehicle和VehicleFactory这两个抽象,至于工厂返回的是Car还是Motorcycle,它根本不关心。下次新增一个Truck,你只需要加Truck类和TruckFactory,老代码一行不动。
不过我得提醒一点:工厂模式不要滥用。如果你的类只有一个实现,短期也没有扩展的可能,直接new反而是最清晰的。为了“以后的扩展”提前建五个工厂类,属于过度设计。
2.3 单例与建造者:特殊场景的特殊约束
单例模式恐怕是争议最大的一个模式。它保证一个类全局只有一个实例,常用于配置管理器、线程池、数据库连接池等重对象。实现上有饿汉、懒汉、双重检查锁、静态内部类、枚举等写法。
我自己的建议很简单:如果你用Java,直接优先考虑枚举实现,或者静态内部类方式。双重检查锁容易写错,我见过好几次instance没有加volatile,在并发环境下拿到半初始化对象的线上事故。枚举实现最简洁:
java复制public enum ConfigManager {
INSTANCE;
private String configPath = "/etc/app/config.yml";
public String getConfigPath() {
return configPath;
}
}
但更重要的是:单例在现在的工程里,很多时候是反模式。它本质是个全局可变状态,会让依赖关系变隐晦,测试也难以隔离。现在更推荐的做法是用依赖注入容器(Spring的Bean默认单例)来管理对象的生命周期,把“单例”的职责交给框架,而不是自己手写。这个思路值得展开说:设计模式的最终目的是提高可维护性,如果某种用法反而让代码更难测试、更难扩展,那模式再“正宗”也是错的。
建造者模式的实用价值则直观得多——解决构造方法参数太多、可读性差的问题。特别是项目里那些五六个必填字段加七八个可选字段的实体类,用构造函数硬怼可读性极差。用建造者流式设置,代码瞬间变成了自然语言:
java复制public class Report {
private String title;
private String header;
private List<String> sections;
private String footer;
private Report() {}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Report report = new Report();
public Builder title(String title) {
report.title = title;
return this;
}
public Builder header(String header) {
report.header = header;
return this;
}
public Builder section(String section) {
if (report.sections == null) {
report.sections = new ArrayList<>();
}
report.sections.add(section);
return this;
}
public Builder footer(String footer) {
report.footer = footer;
return this;
}
public Report build() {
return report;
}
}
}
// 使用
Report report = Report.builder()
.title("月度经营分析")
.header("内部资料")
.section("收入概览")
.section("成本结构")
.footer("数据截止至本月末")
.build();
创建型模式的选择,我总结成一张表:
| 场景 | 推荐模式 | 关键理由 |
|---|---|---|
| 对象创建逻辑简单,但不想硬编码 | 工厂方法 | 面向接口创建,符合DIP |
| 需要创建风格统一的多系列产品 | 抽象工厂 | 保证产品族一致性 |
| 全局唯一重型对象,且能接受框架管理 | 依赖注入/枚举单例 | 避免手写并发隐患 |
| 构造参数过多、字段可选性强 | 建造者 | 提升可读性,防止参数错位 |
| 对象复制成本高,且原型差异小 | 原型 | 以克隆代替重新构建 |
3. 结构与组合:让类之间的关系变得灵活
结构型模式的主题词是**“关系”**——类与类之间怎么组合,才能既灵活又清晰。很多人写代码时线性的“A调用B、B调用C”思维很强,但一旦关系复杂,就不知道怎么组织。
3.1 继承是强耦合,组合才是常态
继承是代码复用里最容易被滥用的手段。你发现两个类有重复代码,于是提取一个父类,让它们继承。短期看代码是少了,但继承带来的问题很隐蔽:父类的方法对子类不一定都合理,父类改动可能影响所有子类,继承层次深了以后根本不敢动。
设计模式传递的核心品味是:优先使用组合而非继承。组合就是“一个类持有另一个类的引用,通过调用它的方法来复用能力”。装饰器、策略、适配器这些模式,本质都是组合思想的体现。装饰器模式尤为典型——它让你像搭积木一样,一层一层地给核心对象附加新能力:
java复制public interface DataSource {
void write(String data);
String read();
}
// 核心实现:读写文件
public class FileDataSource implements DataSource {
private final String filename;
public FileDataSource(String filename) {
this.filename = filename;
}
@Override
public void write(String data) {
System.out.println("写入文件 " + filename + ":" + data);
}
@Override
public String read() {
return "文件内容";
}
}
// 装饰器:加密
public class EncryptionDecorator implements DataSource {
private final DataSource wrappee;
public EncryptionDecorator(DataSource source) {
this.wrappee = source;
}
@Override
public void write(String data) {
wrappee.write(encrypt(data));
}
@Override
public String read() {
return decrypt(wrappee.read());
}
private String encrypt(String data) {
return "[加密]" + data;
}
private String decrypt(String data) {
return data.replace("[加密]", "");
}
}
// 使用
DataSource source = new EncryptionDecorator(new FileDataSource("report.txt"));
source.write("销售额 1000 万");
看到没有,EncryptionDecorator没有改变FileDataSource的源码,只是包了一层就把能力增强了。如果你还想加压缩,再写一个CompressionDecorator包在外面就行。这就是开闭原则直观到不能再直观的演示。
3.2 适配器、装饰器、代理:三个“中间层”的差异
这三个模式长得非常像——都是在一个类外面再包一层。但职责完全不同,我把它们的区别列成对照表:
| 模式 | 核心目的 | 典型场景 | 包装层是否改变接口 |
|---|---|---|---|
| 适配器 | 让不兼容的接口变得兼容 | 对接第三方SDK、老系统接口 | 改变接口,让目标接口统一 |
| 装饰器 | 动态增强对象功能 | 加缓存、加日志、加加密、加压缩 | 不改变接口,保持原接口 |
| 代理 | 控制对象的访问方式 | 延迟加载、权限校验、远程调用 | 不改变接口,但控制访问时机 |
我在真实项目里最常用的判断方法是:先问自己包装这一层的目的。如果是为了“接口形状不一样,要转一下”,那是适配器;如果是为了“原功能不变,但再加点料”,那是装饰器;如果是为了“调用之前的权限校验、调用之后的日志记录”,那是代理。Spring AOP底层的动态代理,就是一个活生生的代理模式案例。
3.3 外观、组合、享元:封装复杂度、构建树、共享细节
还有三个结构型模式,在特定场景下非常有用:
- 外观模式:给复杂子系统提供一个简单的门面接口。比如你接一个短信平台,底层有验证码服务、营销短信服务、状态报告服务,调用方不需要知道这些,只需要一个
SmsFacade.send()方法。它解决的是“接口太杂、调用者晕头转向”的问题。 - 组合模式:将对象组织成树形结构,并且让单个对象和组合对象拥有统一的操作方式。最经典的就是菜单系统——一个菜单项可以包含子菜单项,递归调用
render()方法即可。我封装报表模板的时候也用过这个思路,模板里可以嵌套子模板,渲染时递归处理,非常舒服。 - 享元模式:通过共享来减少对象创建。典型的应用是String常量池、Integer缓存。不过说实话,它在日常业务开发中出场率不高,写底层框架的人用得更多一些。了解它的思想即可,不用硬套。
结构型模式学下来,我最大的体会是:写代码之前先画一画对象关系图,问问自己“这个A真的要继承B吗?还是持有B的引用就行?” 这一个习惯,帮我消灭了一大半臃肿的继承体系。
4. 行为与流程:把“变”从“不变”中拿出去
行为型模式是23种里数量最多的一组,也是最贴近业务逻辑的一组。如果说创建型管“对象怎么来”、结构型管“对象怎么搭”,行为型管的就是“对象之间怎么协作、流程怎么走”。
4.1 策略模式:算法族的运行时切换
这是我在业务代码里用得最多的行为型模式。它的核心思想是:定义一组可互换的算法,把它们各自封装起来,让算法可以独立于调用方变化。
回看开头那个运费计算的例子,代码大概长这样:
java复制public interface FreightStrategy {
int calculate(int weight, int amount);
}
// 按重量计算
public class WeightFreightStrategy implements FreightStrategy {
@Override
public int calculate(int weight, int amount) {
return weight * 5;
}
}
// 大促:按满减计算
public class PromotionFreightStrategy implements FreightStrategy {
@Override
public int calculate(int weight, int amount) {
return amount >= 500 ? 0 : 20;
}
}
// 会员:免运费
public class VipFreightStrategy implements FreightStrategy {
@Override
public int calculate(int weight, int amount) {
return 0;
}
}
public class CheckoutService {
private FreightStrategy strategy;
public void setFreightStrategy(FreightStrategy strategy) {
this.strategy = strategy;
}
public int settle(int weight, int amount) {
// 中间省略商品校验、优惠计算等逻辑
return strategy.calculate(weight, amount);
}
}
以后要加一个“新客首单免运费”规则,不需要动CheckoutService,新建一个NewCustomerFreightStrategy,在配置层设置进去即可。策略模式把if-else的分支逻辑变成了类的多态分发,这对长期维护的意义是决定性的——分支越积越多,if-else就是技术债的温床。
4.2 观察者:发布订阅的解耦设计
观察者模式解决的场景是“一方的变化,要通知其他多个协作方,但协作方不应该反向依赖触发方”。最经典的案例是事件总线、消息推送。Spring里的ApplicationEvent机制就是观察者模式的成熟实现。
我自己写过一个数据导入工具:文件解析完成后,需要触发数据清洗、指标计算、邮件通知、审计日志四个后续动作。如果用同步串行调用,parse()方法里要写一堆外部依赖;用观察者/事件机制后,parse()只管发布一个ImportFinishedEvent,谁关心这个事件,谁自己监听处理,完全解耦。
观察者模式应该注意一个点:监听器之间不要有先后依赖。如果你要靠两个监听器的执行顺序来保证业务正确性,那说明事件设计得不够内聚,应该合并监听器或者重新设计事件粒度。
4.3 责任链、状态、模板方法:流程编排的几种解法
这三个模式都是处理“流程”的,但切入角度不同。
责任链模式把请求沿着一条链传递,每个节点决定自己处理还是交给下一个。审批流就是最典型的场景(组长审批 -> 经理审批 -> 总监审批)。我封装多级缓存的时候也用过:本地缓存未命中 -> Redis缓存未命中 -> 数据库查询,每一级都是一个节点,命中就终止链条。
java复制public abstract class CacheHandler {
protected CacheHandler next;
public void setNext(CacheHandler next) {
this.next = next;
}
public Object get(String key) {
Object value = doGet(key);
if (value == null && next != null) {
value = next.get(key);
}
return value;
}
protected abstract Object doGet(String key);
}
状态模式处理的是“同一个对象在不同状态下,同一个方法的行为不同”。下单流程里的订单状态(待支付、已支付、已发货、已取消)就是这种场景。用状态模式,可以把每个状态的行为封装成一个状态类,避免switch(state)满天飞。
模板方法模式则是在父类里定好算法骨架,把可变步骤留给子类实现。比如数据导出:步骤永远都是“查询数据 -> 格式化 -> 写文件”,但是不同报表的查询逻辑不同、格式化方式不同。那就写一个抽象父类定义export()完整流程,子类只需要实现queryData()和format()两个抽象方法。
这三种模式的选择关键在于问自己:变化的是处理链条、状态,还是算法的具体步骤? 链条变化用责任链;状态变化引起行为变化用状态模式;算法骨架固定但细节多变用模板方法。
5. 当设计模式遇上多Agent编排:主从模式的本质是“把SubAgent当Tool用”
聊完了23种传统模式,我想把视角拉向现在很火的多Agent应用场景。最近很多讨论里都在传一个很有意思的观点:最新的多Agent设计里的主从模式(Orchestrator-Worker),本质上就是把SubAgent当作一种另类的Tool来调用。
这句话怎么理解?在传统软件开发里,我们封装一个工具函数、一个SDK接口,调用方通过统一接口使用它。在多Agent系统里,一个主Agent(协调者)拿到用户请求后,先做任务拆解,然后把子任务分发给各个SubAgent(子代理),最后汇总结果。这里的SubAgent跟一个搜索工具、一个代码执行工具,在主Agent的“工具箱”里地位是一样的——都是被“调用”的对象,只不过这个工具的能力更复杂、更接近“人”的完整任务执行能力。
用设计模式的眼光看这个架构,会发现它其实融合了好几个经典模式的思路:
- 门面模式(Facade):主Agent对调用方屏蔽了内部所有SubAgent的复杂交互,外部只看到“我提交了一个任务,我拿到一个结果”。
- 策略模式(Strategy):主Agent选择用哪个SubAgent来执行子任务,本质就是策略选择。比如“写代码”子任务分给代码Agent,“查资料”分给检索Agent,这就是运行时按需切换策略。
- 命令模式(Command):把任务请求封装成可输入、可记录、可回传的对象,SubAgent执行完返回结果,整个过程像一个命令分发系统。
- 组合模式(Composite):主Agent和SubAgent构成树状层级,主Agent可以继续下挂子Agent,形成递归的编排能力。
如果让我们用接口抽象来表达“SubAgent即Tool”这个思想,大概长这样:
java复制public interface AgentTool {
String name();
AgentToolResult execute(AgentToolInput input);
}
然后,CodeReviewAgent、WebSearchAgent、DataAnalyzeAgent统统实现AgentTool接口,注册到主Agent的工具表里:
java复制public class OrchestratorAgent {
private final Map<String, AgentTool> tools = new HashMap<>();
public void registerTool(String name, AgentTool tool) {
tools.put(name, tool);
}
public AgentToolResult dispatch(String taskType, AgentToolInput input) {
AgentTool tool = tools.get(taskType);
if (tool == null) {
throw new IllegalArgumentException("未注册的任务类型: " + taskType);
}
// 可以在调用前后加日志、加监控,这就是代理模式的影子
return tool.execute(input);
}
}
这里还能看到代理模式的应用:主Agent在转发任务给SubAgent时,可以统一完成权限校验、上下文拼装、结果校验、失败重试等横切逻辑,而SubAgent自己不需要关心这些。
所以你会发现:设计模式从来不是C++/Java老学究的故纸堆,只要还在写“协作型代码”,模式的思想就一直适用。多Agent编排这种新名词背后的工程本质,和我们十年前在Service层做接口抽象、做策略分发,底层是同一套智慧。当然,我不是说多Agent系统里的主从模式就是某个设计模式的纯实现——它比传统模式复杂得多,因为Agent有自主决策、有上下文记忆、有不确定性。但用设计模式的“稳定-变化”视角去分析它,你就能看透:哪里是稳定不变的骨架(工具抽象、调用流程),哪里是需要扩展的变化点(具体Agent能力),这个分析能力,比记住模式本身更值钱。
6. 设计模式的学习路径与实战心得:从应付考试到写出能扩展的代码
最后一章,我想直接聊点落地的东西。毕竟我看到热搜词里有“设计模式期末”“设计模式大作业”“c++设计模式”“java设计模式”这些关键词,说明很多读者跟我当年一样,正处在一个“必须学、但不知道怎么学透”的阶段。
6.1 给“期末/大作业”选手的快速上手方法
如果时间紧张,目标是先把考试和作业应付过去,我建议按这个策略走:
首先,别按原书顺序背。 GoF原书从抽象工厂开始,对初学者非常不友好。我建议从最容易理解的模式入手:单例、工厂方法、策略、观察者、模板方法、装饰器。这六个模式搞透了,考试里至少六成分数到手。
其次,每个模式用一个“生活化例子+一段最小代码”来记忆。 比如单例=全局唯一配置中心;策略=支付方式切换;观察者=公众号订阅;模板方法=煮咖啡和泡茶的相同步骤。把模式跟一个具体画面绑定,比死背意图和结构强十倍。
再次,大作业要选一个能自然展示多个模式的题目。 我比较推荐“在线商城/订单系统”这类经典题目——因为业务天然包含多角色、多状态、多算法,很适合同时展示工厂模式(创建不同类型商品/订单)、状态模式(订单状态流转)、策略模式(运费/优惠计算)、观察者(下订单后通知库存/邮件)。而且这些模式之间不是硬凑的,是业务真的需要。
在写大作业时,最好在文档里画一张“需求变化点-模式映射表”,用表格说明:哪些业务需求可能会变,你用什么模式隔离了这个变化。这个做法特别加分——它证明你不是抄了一个模式堆砌的项目,而是真的理解了模式的动机。
6.2 实战中的几个关键判断:什么时候别用模式
工作几年之后我对设计模式的态度变了很多。最大的一条感悟是:设计模式是重构的目标,不是编码的起点。
如果你一开始就照着“我要用抽象工厂、我要用访问者”的方向去设计一个连需求都没完全明确的系统,大概率会过度设计。更务实的做法是:第一版用最直白的方式写出来,让代码能跑;随着需求变化,当你在某个类里不断堆新功能、不断加参数、不断改if-else的时候,停下来,识别变化点,用设计模式去重构。
还有几个具体的“别用”场景:
- 只有一种实现时,别用策略模式。一个接口配一个实现,多出的抽象层只是噪音。
- 不需要灵活扩展时,别用工厂模式。项目生命周期短、几乎没有外接变化,直接new反而清晰。
- 单例里不要放可变状态。全局可变状态坑起来根本不是技术问题,是逻辑混乱问题。
- 为了“团队评审显得有深度”而加模式。评审看的是可读性和可维护性,不是模式数量。代码里塞五个强行套用的模式,比写一个朴素的长方法更让人崩溃。
判断标准其实就一条:这个模式是否让改动成本显著下降。 如果加一个模式,下一次需求变更时你的改动从“改5个类”变成“加1个类”,这是值得的;如果从“改1个类”变成“新建2个接口+3个类”,那多半是在过度设计。
6.3 我的个人学习经验与建议
最后分享几条踩了几年坑才沉淀下来的经验。
把读源码当成学模式的最好材料。 如果你用Java,到JDK里去看java.util.Collections、ThreadPoolExecutor、Spring的ApplicationContext,再到MyBatis里看SqlSession的创建过程。源码里的模式不是教学案例,是真实工程环境下经过千锤百炼的取舍,比教科书里的示例深刻得多。我就是在读Spring源码时,才真正理解了工厂和代理是怎么支撑起一个庞大框架的。
写代码前先画“变化图”,再用模式匹配。 我在设计一个模块前,习惯先在草稿纸上列出:这个模块哪些需求是稳定的,哪些是会变的。稳定部分是骨架,变化部分就是模式要切入的位置。这样的设计从一开始就有理有据。
把一个模式用“没有它”和“有它”各写一遍。 学习新模式的最高效办法,就是先写一段没有任何模式、充满if-else的版本,体会痛点;然后重构出模式版本,体会收益。不少初学者只看“有它”的写法,没有真实感受过“没有它”的痛,自然无法理解模式的价值。
模式是交流词汇,不是教条。 当你在团队里说“这块逻辑应该用观察者解耦”时,大家不需要看代码就知道你的意图。设计模式提供了一套高效的沟通语言,这是它的隐藏价值,甚至比代码层面的价值更大。
设计模式这条路,我走了很久才从“背”到“懂”。如果你刚接触,不用焦虑背不住类图;如果你正在期末备考,记住每个模式解决什么变化,比记住类名重要得多;如果你已经在项目里动手实践,也许可以试着用模式去审视自己最近写的代码,看看哪些if-else其实可以变成策略,哪些被new死的依赖可以交给工厂。模式不会让你的代码一夜变好,但它会给你一双看见“变化”的眼睛,而这,才是学习设计模式真正值钱的地方。
