掌握七种常见设计模式:从入门到实践
你是否曾在代码审查时听到 “这里用个工厂模式就好了”,却不太确定对方在说什么?设计模式听起来很高大上,但本质上,它们是前辈们踩坑无数后总结出的 可复用的解决方案。本文带你从零掌握六种最常用的设计模式。

一、什么是设计模式?
1994 年,Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四位作者(人称 GoF,Gang of Four)出版了《设计模式:可复用面向对象软件的基础》一书,将 23 种经典设计模式归纳为三大类:
| 类别 |
核心关注点 |
典型模式 |
| 创建型 |
如何创建对象 |
单例、工厂、建造者 |
| 结构型 |
如何组合对象 |
适配器、装饰器、代理 |
| 行为型 |
对象间如何协作 |
观察者、策略、命令 |
在开始学习具体模式之前,建议先了解 SOLID 原则 —— 它是理解设计模式为何这样设计的思维基础。
二、单例模式(Singleton)
2.1 一句话理解
确保一个类只有一个实例,并提供全局访问点。
2.2 典型场景
- 日志记录器 —— 全局共享同一个日志实例
- 数据库连接池 —— 避免重复创建连接
- 应用配置管理 —— 配置只需加载一次
2.3 Java 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() { }
public static Singleton getUniqueInstance() { if (uniqueInstance == null) { synchronized (Singleton.class) { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
|
2.4 注意事项
- 优点:节省资源,保证全局一致性
- 缺点:违反单一职责原则(管理自身创建 + 业务逻辑),增加单元测试难度
- 现代开发中,依赖注入(DI)往往是更好的全局共享方案
2.5 优缺点
优点
缺点
三、工厂模式(Factory)
3.1 一句话理解
将对象创建逻辑封装起来,客户端不需要知道具体类的名字。
3.2 典型场景
- 支付系统 —— 根据渠道创建不同的支付处理器(微信、支付宝、银行卡)
- 通知服务 —— 根据类型创建不同的通知方式(短信、邮件、推送)
- 数据库驱动 —— 根据配置动态切换 MySQL、PostgreSQL、SQLite
3.3 Java 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| interface Device { void operate(); }
class Phone implements Device { @Override public void operate() { System.out.println("手机操作:开机 -> 显示主界面 -> 关机\n"); } }
class Computer implements Device { @Override public void operate() { System.out.println("电脑操作:开机 -> 加载系统 -> 关机\n"); } }
class DeviceFactory { public static Device createDevice(String type) { if (type.equalsIgnoreCase("PHONE")) { return new Phone(); } else if (type.equalsIgnoreCase("COMPUTER")) { return new Computer(); } return null; } }
public class SimpleFactoryDemo { public static void main(String[] args) { Device phone = DeviceFactory.createDevice("PHONE"); Device computer = DeviceFactory.createDevice("COMPUTER"); phone.operate(); computer.operate(); } }
|
3.4 三种变体
- 简单工厂:一个工厂类根据参数创建不同对象(如上例)
- 工厂方法:父类定义创建接口,子类决定创建哪种对象
- 抽象工厂:创建一组相关对象,确保它们风格一致。它提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们具体的类。可以理解为它是“工厂的工厂”
3.5 优缺点
简单工厂模式:
- 优点:结构简单,将创建逻辑集中管理,实现了职责分离。
- 缺点:违反开闭原则,新增产品需要修改工厂类。
工厂方法模式:
- 优点:符合开闭原则,扩展性好,创建逻辑被分散到各个具体工厂中,符合单一职责原则。
- 缺点:每增加一个产品,就需要增加一个具体工厂类,会导致类数量成倍增加,增加了系统的复杂性。
抽象工厂模式:
- 优点:非常适合用于创建一系列相互匹配的产品。切换整个产品族非常方便,只需更换具体的工厂即可。
- 缺点:扩展新的产品等级结构困难。例如,如果产品族需要增加一个“鼠标”,那么
AbstractFactory 接口就需要增加一个 createMouse() 方法,所有已经实现的具体工厂类也都要跟着修改,这违反了开闭原则。
| 特性 |
简单工厂模式 |
工厂方法模式 |
抽象工厂模式 |
| 复杂度 |
低 |
中 |
高 |
| 关注点 |
创建单一类型的产品 |
创建单一类型的产品 |
创建一族相关联的产品 |
| 开闭原则 |
违反 |
遵守 |
对扩展产品族遵守,对扩展产品类违反 |
| 核心 |
一个集中式工厂 |
将创建逻辑延迟到子类 |
创建产品家族 |
四、观察者模式(Observer)
4.1 一句话理解
定义一对多的依赖关系,当一个对象状态变化时,所有依赖者自动收到通知。
4.2 典型场景
- 消息推送 —— 用户订阅话题后,有新内容自动通知
- 数据绑定 —— 数据变化时 UI 自动刷新(Vue、React 的响应式系统)
- 事件总线 —— 模块间松耦合通信
4.3 Java 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| interface Subscriber { void onMessage(String topic, String content); }
class MessageBroker { private Map<String, List<Subscriber>> subscribers = new HashMap<>();
public void subscribe(String topic, Subscriber subscriber) { subscribers.computeIfAbsent(topic, k -> new ArrayList<>()).add(subscriber); }
public void unsubscribe(String topic, Subscriber subscriber) { List<Subscriber> list = subscribers.get(topic); if (list != null) { list.remove(subscriber); } }
public void publish(String topic, String content) { List<Subscriber> list = subscribers.get(topic); if (list != null) { for (Subscriber s : list) { s.onMessage(topic, content); } } } }
MessageBroker broker = new MessageBroker();
broker.subscribe("tech", (topic, content) -> System.out.println("收到 [" + topic + "]: " + content));
broker.publish("tech", "设计模式真有趣!");
|
4.4 注意事项
- 务必在不需要时取消订阅,否则可能导致内存泄漏
- 大量观察者时注意性能,可以考虑异步通知
4.5 优缺点
优点
缺点
- 潜在的性能问题
- 可能导致意外的级联或循环
- 调试困难
五、策略模式(Strategy)
5.1 一句话理解
定义一组可互换的算法,让算法可以独立于客户端变化。
5.2 典型场景
- 排序 —— 运行时切换不同排序算法
- 折扣计算 —— 满减、打折、立减等多种策略
- 数据压缩 —— gzip、brotli、lz4 等算法互换
- 表单验证 —— 不同字段使用不同的验证规则
5.3 Python 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| from abc import ABC, abstractmethod
class DiscountStrategy(ABC): @abstractmethod def apply(self, price: float) -> float: pass
class NoDiscount(DiscountStrategy): def apply(self, price: float) -> float: return price
class PercentageDiscount(DiscountStrategy): def __init__(self, percent: float): self.percent = percent
def apply(self, price: float) -> float: return price * (1 - self.percent / 100)
class FullReductionDiscount(DiscountStrategy): def __init__(self, threshold: float, reduction: float): self.threshold = threshold self.reduction = reduction
def apply(self, price: float) -> float: if price >= self.threshold: return price - self.reduction return price
class Order: def __init__(self, total: float): self.total = total
def checkout(self, strategy: DiscountStrategy) -> float: return strategy.apply(self.total)
order = Order(300) print(order.checkout(NoDiscount())) print(order.checkout(PercentageDiscount(20))) print(order.checkout(FullReductionDiscount(200, 50)))
|
策略模式最大的好处是消灭了长长的 if-else 链 —— 新增一个策略只需要新建一个类,符合开闭原则。
六、装饰器模式(Decorator)
6.1 一句话理解
在不修改原对象的情况下,动态地给对象添加额外行为。
6.2 典型场景
- I/O 流 ——
BufferedReader 装饰 FileReader
- 中间件链 —— HTTP 请求依次经过日志、鉴权、压缩中间件
- UI 组件 —— 给文本框加边框、滚动条、阴影等
- 日志增强 —— 给函数加计时、重试、缓存
6.3 Python 实现(函数装饰器)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import time import functools
def retry(max_attempts: int = 3, delay: float = 1.0): """装饰器:给函数添加自动重试能力""" def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): import logging for attempt in range(1, max_attempts + 1): try: return func(*args, **kwargs) except Exception as e: if attempt == max_attempts: raise logging.warning( f"[{func.__name__}] 第 {attempt} 次失败,{delay}s 后重试: {e}" ) time.sleep(delay) return None return wrapper return decorator
def log_execution(func): """装饰器:记录函数执行时间""" @functools.wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) elapsed = time.time() - start print(f"[{func.__name__}] 执行耗时: {elapsed:.3f}s") return result return wrapper
@retry(max_attempts=3, delay=0.5) @log_execution def fetch_data(url: str): import random if random.random() < 0.5: raise ConnectionError("网络异常") return f"来自 {url} 的数据"
print(fetch_data("https://api.example.com/data"))
|
面向对象的装饰器和 Python 函数装饰器共享同一个核心理念:通过组合而非继承来扩展功能。
七、适配器模式(Adapter)
7.1 一句话理解
将一个接口转换成客户端期望的另一个接口,让不兼容的类可以一起工作。
7.2 典型场景
- 第三方库集成 —— 统一不同短信服务商的 API
- 遗留系统改造 —— 新系统调用老的接口
- 数据格式转换 —— XML 转 JSON、CSV 转 DataFrame
7.3 Java 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class ThirdPartyLogger { public void logMessage(String msg) { System.out.println("[ThirdParty] " + msg); } }
interface Logger { void info(String message); void error(String message); }
class LoggerAdapter implements Logger { private ThirdPartyLogger adaptee;
public LoggerAdapter(ThirdPartyLogger adaptee) { this.adaptee = adaptee; }
@Override public void info(String message) { adaptee.logMessage("[INFO] " + message); }
@Override public void error(String message) { adaptee.logMessage("[ERROR] " + message); } }
ThirdPartyLogger oldLogger = new ThirdPartyLogger(); Logger logger = new LoggerAdapter(oldLogger); logger.info("用户登录成功"); logger.error("数据库连接失败");
|
适配器的精髓在于不修改原有代码的前提下让新旧系统协作 —— 这在微服务拆分和渐进式重构中特别有用。
7.4 优缺点
优点
- 增强了类的复用性:可以复用已存在的、功能强大的
Adaptee 类,而无需修改其源码。
- 提高了灵活性和扩展性:可以非常方便地替换或增加新的适配器,来适配不同的
Adaptee,符合开闭原则。
- 解耦:将客户端(Client)与具体的实现类(Adaptee)解耦,客户端只需要和目标接口(Target)打交道。
缺点
- 增加了系统的复杂性:每适配一个类都需要增加一个适配器类,如果过度使用,会导致系统中的类数量增多,代码可读性有所下降。
- (针对类适配器)限制较多:由于语言的单继承限制,类适配器一次最多只能适配一个
Adaptee 类,并且要求 Target 必须是接口或抽象类。
八、代理模式(Proxy)
8.1 一句话理解
为另一个对象提供一个替身或占位符,以控制对这个对象的访问。
8.2 典型场景
- 虚拟代理(Virtual Proxy)—— 大对象的延迟加载(如图片、数据库连接)
- 远程代理(Remote Proxy)—— 代表远程对象进行通信(如 RMI、RPC)
- 保护代理/安全代理(Protection Proxy)—— 控制访问权限(如安全代理、智能指引)
- 智能引用(Smart Reference)—— 在访问对象时执行额外操作(如计数、日志)
8.3 Java 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| public interface SmsService { String send(String message); }
public class SmsServiceImpl implements SmsService { public String send(String message) { System.out.println("send message:" + message); return message; } }
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class DebugInvocationHandler implements InvocationHandler {
private final Object target;
public DebugInvocationHandler(Object target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { System.out.println("before method " + method.getName()); Object result = method.invoke(target, args); System.out.println("after method " + method.getName()); return result; } }
public class JdkProxyFactory { public static Object getProxy(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new DebugInvocationHandler(target) ); } }
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl()); smsService.send("java");
|
九、如何选择合适的设计模式?
在 90 秒内快速定位正确的模式,可以问自己四个问题:
- 什么在变化? —— 对象创建方式在变 → 创建型模式;行为在变 → 行为型模式
- 需要互相替换吗? —— 算法互换 → 策略模式;功能增强 → 装饰器模式
- 对象间如何通信? —— 一对多通知 → 观察者模式;复杂调用链 → 责任链模式
- 接口不兼容怎么办? —— 加个适配器
十、总结
| 模式 |
一句话 |
关键词 |
| 单例 |
全局唯一实例 |
共享、节省资源 |
| 工厂 |
封装对象创建 |
解耦、可扩展 |
| 观察者 |
状态变化自动通知 |
发布-订阅、松耦合 |
| 策略 |
算法族可互换 |
消除 if-else |
| 装饰器 |
动态添加行为 |
组合优于继承 |
| 适配器 |
接口转换 |
兼容、渐进式改造 |
| 代理 |
控制对象访问 |
替身、权限控制 |
设计模式不是银弹,不要为了用而用。Martin Fowler 说过:“软件中的模式,与其说是规范,不如说是建议。” 当你发现某个问题天然吻合一个模式的形状时,那就是使用它的最佳时机。