☰
设计模式实战指南:三大类型与高频模式详解及代码重构
2026/10/20 21:26:13 网站建设 项目流程

2. 按类别理解:创建型、结构型、行为型的底层逻辑

设计模式分三大类,这不是随便分的,背后其实是软件设计的三个核心问题:对象怎么创建、对象怎么组合、对象怎么协作。我当年学的时候觉得这个分类只是目录,后来才发现它是理解模式的钥匙。

2.1 创建型模式:解决对象“怎么来”的问题

创建型模式的核心思想是把对象的创建过程从使用代码中独立出来。为什么要独立?举个例子,你写了一个工具类,里面直接new了某个对象。结果这个对象的构造函数要加参数了,你得改多少处调用?创建型模式就是帮你把“怎么创建”封装起来,让调用方只关心“我要什么”,不关心“怎么来的”。

包括 5 个模式:工厂方法、抽象工厂、单例、建造者、原型。它们各自的侧重点不一样,工厂系解决“创建逻辑复杂”的问题,单例解决“全局只允许一个实例”的问题,建造者解决“对象构造参数太多”的问题,原型解决“创建成本高,通过拷贝复制”的问题。

2.2 结构型模式:解决对象“怎么组合”的问题

结构型模式关注的是类和对象的组合方式,目标是让组合出来的结构既灵活又高效。编程里最常见的需求就是“给现有对象加功能”,你怎么加?直接改源码?破坏了开闭原则。继承扩展?有时候会搞得类爆炸。

7 个结构型模式里,适配器解决“接口不兼容”的问题,装饰器解决“动态追加功能”的问题,代理解决“不想直接访问原对象”的问题,外观解决“子系统太复杂,提供一个简化门面”的问题。它们的共同逻辑是:组合优于继承,接口隔离变化。

2.3 行为型模式:解决对象“怎么协作”的问题

行为型模式最容易被忽视,但其实它是工作中用得最多的一类,核心是对象之间的职责分配和通信方式。很多业务 bug 就出在对象之间的交互逻辑乱成一团——你调我我调你,谁改了状态别人都不知道。

11 个行为型模式里,策略解决“算法族可以互相替换”的问题,观察者解决“状态变化要通知多个依赖方”的问题,模板方法解决“骨架固定、步骤可定制”的问题,责任链解决“请求需要多个处理器依次处理”的问题。行为型模式关注的是运行时的行为编排,所以它的抽象程度更高,理解起来也更烧脑。

3. 高频模式逐个拆解:从代码到场景的实战对照

GoF 的 23 个模式是基础,但日常工作真正常用的其实就那七八个。我建议你把高频模式吃透,剩下的混个脸熟、知道什么时候去查就够了。下面挑四个代表性最强的模式展开讲。

3.1 单例模式:看似简单,坑最多

单例的核心就一句话:保证一个类只有一个实例,并提供一个全局访问点。典型的应用场景有配置管理器、线程池、数据库连接池、日志对象。

基于常见实践的补充:很多人以为单例就是“一个静态变量 + 一个私有构造函数”,但实际工程里单例分两种写法——饿汉式和懒汉式,多线程环境下的线程安全问题才是真正的考点。饿汉式在类加载时就初始化,无并发问题,但可能造成资源浪费;懒汉式在首次调用时创建,需要加锁处理。

// 双重检查锁(DCL)方式,兼顾性能与线程安全 public class ConfigManager { // volatile 防止指令重排导致拿到未初始化完成的对象 private static volatile ConfigManager instance; private ConfigManager() { // 加载配置文件 } public static ConfigManager getInstance() { if (instance == null) { synchronized (ConfigManager.class) { if (instance == null) { instance = new ConfigManager(); } } } return instance; } }

注意:反射可以通过setAccessible(true)强行调用私有构造函数,序列化也可能破坏单例。如果不考虑这些极端情况,面试时可以提一下,但实际业务里用枚举实现单例是更稳妥的方案。

这是我在实际项目里踩过的一个坑:某次把单例里的成员变量当成了“可在任意地方修改的全局变量”,结果多个线程同时写,数据被串成了麻花。单例只保证“一个实例”,不保证“线程安全”,实例内部的字段该加锁还得加锁,该用原子类还得用原子类。

3.2 策略模式:干掉一坨 if-else 的最优解

策略模式是我个人在工作中用得最多的模式之一。它的核心是定义一族算法,把它们封装起来,并且可以互相替换。经典的场景是支付方式选择、营销活动规则、文件导出格式。

我试过用策略模式重构一个支付模块,原来 30 多个 if-else 嵌套了 3 层,每加一种支付方式就得动主流程,改完一个地方崩三处。重构后每种支付方式一个策略类,主流程只依赖一个策略接口,新接入支付宝类的时候不动任何已有代码。

// 策略接口 public interface PayStrategy { void pay(BigDecimal amount); } // 具体策略:微信支付 public class WechatPayStrategy implements PayStrategy { @Override public void pay(BigDecimal amount) { // 微信支付逻辑 } } // 具体策略:支付宝 public class AlipayStrategy implements PayStrategy { @Override public void pay(BigDecimal amount) { // 支付宝逻辑 } } // 策略上下文:持有策略并委托执行 public class PayContext { private final PayStrategy strategy; public PayContext(PayStrategy strategy) { this.strategy = strategy; } public void executePay(BigDecimal amount) { strategy.pay(amount); } }

策略模式的精髓在于把变化的部分抽出来,用组合的方式代替继承。判断一个地方是否适合用策略模式,可以看两个信号:一是有没有多个类只改行为不改身份,二是有没有大量条件分支按类型或枚举值执行不同逻辑。

3.3 观察者模式:事件驱动架构的基石

观察者模式解决的是一对多的依赖关系:当被观察者状态变化时,所有依赖它的观察者都能得到通知并自动更新。典型场景有消息推送、事件总线、GUI 按钮点击监听、商品库存变化触发的后续动作。

以一个电商下单场景为例:用户下单后,需要同时做扣库存、发短信、发优惠券、记日志。如果这些逻辑都写在下单方法里,那下单方法会越来越肿,而且每加一个动作都要改订单核心代码。用观察者模式,下单成功后只发布一个“订单创建事件”,各个监听器自己去处理各自的逻辑。

// 事件发布/订阅的简化实现 public class OrderEventPublisher { private final List<OrderEventListener> listeners = new ArrayList<>(); public void subscribe(OrderEventListener listener) { listeners.add(listener); } public void publish(OrderEvent event) { for (OrderEventListener listener : listeners) { listener.onOrderEvent(event); } } }

实操心得:观察者模式有个隐患是观察者执行顺序不确定,如果两个监听器之间有依赖关系,就容易出诡异 bug。我在项目里处理这个问题时,给监听器加了@Order(n)注解,按顺序执行,同时强调监听器之间尽量解耦,不依赖彼此的执行结果。

3.4 工厂模式:从简单工厂到抽象工厂的递进

工厂模式的核心是用工厂方法代替直接 new。但很多人分不清简单工厂、工厂方法、抽象工厂三者的区别,我把它们的区别整理成了一张对照表。

模式核心思想扩展方式适用场景
简单工厂一个工厂类根据参数返回不同产品加产品要改工厂类产品种类少、变化少
工厂方法每个产品对应一个工厂子类加产品加一个工厂类产品种类多、扩展频繁
抽象工厂一个工厂生产一族相关产品加一族产品加一个工厂类系统需要多套产品族

用一句话总结:简单工厂是“一次判断”,工厂方法是“一个产品一个工厂”,抽象工厂是“一个工厂管一窝产品”。工作中大部分场景用简单工厂就够了,直接套抽象工厂反而显得过度设计。

4. 从需求到模式:怎么判断“这里该用哪个”

很多初学者最大的困惑是:模式都背下来了,拿到一个需求还是不知道怎么套。我的经验是,不要从模式出发找需求,要从需求出发找模式。下面用一个真实需求来分析推导过程。

4.1 需求信号识别:什么时候该动用设计模式

我总结了几种常见的“代码坏味道”,每种对应到的候选模式:

  • 构造函数参数越写越多,调用方分不清顺序 → 建造者模式
  • 一大串 if-else / switch 分支,每种分支做不同的事 → 策略模式或工厂模式
  • 一个类同时负责多个职责,比如既做数据校验又做日志又做展示 → 责任链模式或者门面模式
  • 对象创建过程复杂,依赖配置、依赖环境 → 工厂模式
  • 要给老类加新功能,但不想改动已有代码 → 装饰器模式
  • 多个类结构相似,细节不同→ 模板方法模式

这套信号不是一次就能形成的,建议每写一次代码,都回头看看是否有可以替换成模式的“坏味道”。

4.2 一个完整推导实例:从 if-else 到策略模式的演进

假设现在要做一个消息推送系统,支持短信、邮件、站内信三种推送方式。第一版代码很自然地就写成了这样:

public void push(String type, String content, String target) { if ("sms".equals(type)) { // 发短信逻辑 } else if ("email".equals(type)) { // 发邮件逻辑 } else if ("inbox".equals(type)) { // 发站内信逻辑 } }

上面的代码初看没什么问题,但业务一定会演化:新增微信推送、新增 App 推送、不同渠道的推送还要做不同的日志记录和重试策略。每加一个渠道,就要动这个方法,而且所有人都要去读这个越来越长的方法。

这时候就该引出策略模式。我们把“推送”这个行为抽象成接口,把每种渠道封装成独立策略,再用一个路由类根据 type 选出对应的策略:

public interface PushStrategy { void push(String content, String target); } public class SmsPushStrategy implements PushStrategy { } public class EmailPushStrategy implements PushStrategy { } public class InboxPushStrategy implements PushStrategy { } // 策略路由:通过 Map 维护类型与策略的映射 public class PushStrategyRouter { private final Map<String, PushStrategy> strategyMap = new ConcurrentHashMap<>(); public PushStrategyRouter() { strategyMap.put("sms", new SmsPushStrategy()); strategyMap.put("email", new EmailPushStrategy()); strategyMap.put("inbox", new InboxPushStrategy()); } public void route(String type, String content, String target) { PushStrategy strategy = strategyMap.get(type); if (strategy == null) { throw new UnsupportedOperationException("unsupported push type: " + type); } strategy.push(content, target); } }

重构完后,主流程代码量减少了一大半,新增微信推送只需要加一个实现类再注册到 Map 里,完全不需要改动 router 之外的既有代码。这是策略模式实战价值最直观的体现。

补充:你可能会问,工厂模式和策略模式在这个例子里看起来很像,区别在哪?我的理解是:工厂模式关心“对象怎么创建”,策略模式关心“对象创建完之后怎么用它”。实际组合使用也很常见,先用工厂创建策略,再用策略执行逻辑。

5. 常见误区与排查技巧:为什么你学了设计模式还是不会用

设计模式的学习曲线比较陡,大部分人都经历过“背了忘、忘了背”的循环。下面这些坑是我自己和身边同事都踩过的,整理出来供你对照自查。

5.1 误区一:为了用模式而用模式,过度设计

我见过最夸张的例子:一个只有两张表的小项目,愣是用了抽象工厂加建造者加门面。代码是“很设计”,但维护的人想骂人。设计模式是工具,不是装饰品,是代码出现“坏味道”时才拿出来用的,不是写代码之前就先预定的“秀场”。

判断标准很简单:引入模式后,代码是变得更简洁了,还是变得更绕了?如果增加两层抽象只是让代码“看起来高级”,那就不值得。

5.2 误区二:只看类图,不写代码

很多人学设计模式就是把 UML 类图背下来,画得清清楚楚,但一写代码就卡住。我强烈建议:每学一个模式,就用 Java 手写一个最小可运行的例子,不要抄书上的,就自己从零写,写不出来再去看示例。写一遍比看十遍管用。

5.3 误区三:忽略了模式的适用条件

每个设计模式都有它适用的“温度范围”。比如模板方法模式适合方法内部骨架固定、步骤可变的情况,但如果你发现子类需要覆写一半以上的方法,说明这个模板抽象得不对,这个模式就不该硬用。观察者模式适合一对多通知,但如果只有一个观察者,直接依赖就好了,何必绕一圈。

我个人的排查步骤是:

  1. 先描述现状:当前的代码坏味道是什么?具体是哪一种?
  2. 再考虑模式:哪个模式能消除这个坏味道?它引入了哪些新结构?
  3. 权衡取舍:引入这个模式带来的复杂度和它解决的问题相比,值不值?

6. 学习路径建议:用“三个循环”真正吃透设计模式

6.1 第一个循环:对照生活类比建立直觉

设计模式其实很多都能用生活场景类比。策略模式就像你出门选交通工具,地铁、公交、打车都能到,具体选哪个运行时定;观察者模式就像你关注的主播开播了,所有粉丝同时收到通知;门面模式就像酒店前台,你不需要知道背后有多少部门,找前台一个口就行。

先建立直觉,再看代码模型,会顺畅得多。

6.2 第二个循环:在开源代码里找“实物”

学完一个模式后,去 Spring、MyBatis、JDK 源码里找它的身影。比如 JDK 里的Runtime类就是单例,java.util.Collections#synchronizedList就是静态代理,java.util.List的Iterator就是迭代器模式,Spring 的BeanFactory就是抽象工厂,Spring 的ApplicationListener就是观察者模式。看源码里怎么用,比看任何教程都有说服力。

6.3 第三个循环:重构自己的老代码

打开你三个月前写的一个业务模块,看看里面的 if-else 能不能用策略模式重构成路由?能不能用模板方法把重复的流程抽出来?能不能用观察者把耦合在一起的通知逻辑拆开?

我个人在实际操作中的体会是:设计模式不是做项目时必须交的作业,而是代码变复杂后的逃生通道。我会在自己的代码里刻意埋一些“重构种子”——比如先写一版简单的实现,功能跑通后,再看看哪些地方有坏味道,然后动手用模式优化。这样练几次,模式的适用场景就长在脑子里了,遇到需求时自己会冒出来。

最后再分享一个小技巧:不要强迫自己一次把 23 个模式全背完,第一个月熟练掌握单例、工厂、策略、观察者、模板方法这五个就足够了,剩下的是遇到问题再去查、去用。真正搞懂设计模式的标志不是“我能画出所有类图”,而是“看到一段烂代码,我能顺手把它修好”。这个目标,靠背是达不到的,靠练才行。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询