设计模式
常说的 23 种设计模式出自 GoF(Gang of Four,"四人帮")——1994 年《设计模式:可复用面向对象软件的基础》一书的四位作者,这本书至今仍是该领域的开山之作。23 种模式分为创建型、结构型、行为型三大家族,本文照此归类。
| 家族 | 关注的问题 | 收录模式(加粗为高频) |
|---|---|---|
| 创建型 | 对象怎么"生" | 单例、工厂方法、建造者、抽象工厂、原型 |
| 结构型 | 类与对象怎么"组装" | 适配器、装饰器、代理、外观、组合、桥接、享元 |
| 行为型 | 对象之间怎么"协作" | 策略、观察者、责任链、模板方法、命令、状态、迭代器(低频速查) |
| 其他经典 | 不属于 GoF 但同样常用 | 生产者-消费者 |
创建型模式
单例模式
高频 确保一个类全局只有一个实例,并提供统一的访问点。常见实现有五种:懒汉式
在首次被调用时才创建实例。
synchronized 关键字在争用激烈的场景下,内置锁会升级为重量级锁,开销大、性能差,所以不推荐高并发线程使用这种方式的单例模式。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
}饿汉式
在类加载时就创建实例。
饿汉单例模式的优点是足够简单、安全。缺点是单例在类加载时实例直接初始化了,很多时候,类加载时并不需要进行单例初始化。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}双重检查锁
在多线程环境下使用懒加载,并且保证线程安全。
实际上,单例模式的加锁操作只有单例在第一次创建时才需要,在创建时保证只有一个线程能获取锁即可,之后的单例获取操作都没必要再加锁。
public class Singleton {
// volatile 防止指令重排:new 分为分配内存、初始化、赋值引用三步,若发生重排,其他线程可能拿到未初始化的实例
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
// 第一次判断null可能多个线程都能通过,通过加锁操作,保证只有一个线程能创建。
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}静态内部类
利用类加载机制保证线程安全,且在使用时才加载内部类。
双重检查锁比较复杂,写法烦琐;静态内部类实现懒汉式单例模式也能保证线程安全,并且易于理解,推荐使用此种方式。
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}枚举
利用枚举的特性保证线程安全且实现简洁。
public enum Singleton {
INSTANCE;
// 可以添加其他方法或属性
}框架源码中的它
- JDK:
Runtime#getRuntime()是饿汉式的经典实现,JVM 全局一个; - Spring:Bean 默认 scope 就是单例,但注意是容器级单例(一个容器一份),与 JVM 级单例不是一回事;
- 枚举单例是《Effective Java》推荐的最终答案:天然防反射攻击、防反序列化破坏。
工厂方法模式
高频 简单工厂把所有 new 的判断集中在一处,新增产品就得改工厂代码——违背开闭原则。工厂方法把"造什么"下放给子类:父类定契约,子类定产品,新增产品只需新增一对"产品 + 工厂",原有代码一行不动。// 产品接口
public interface Product {
void use();
}
// 具体产品A
public class ProductA implements Product {
@Override
public void use() {
System.out.println("使用产品A");
}
}
// 具体产品B
public class ProductB implements Product {
@Override
public void use() {
System.out.println("使用产品B");
}
}
// 工厂接口:造什么,由子类决定
public interface Factory {
Product create();
}
// A 的专属工厂
public class FactoryA implements Factory {
@Override
public Product create() {
return new ProductA();
}
}
// B 的专属工厂
public class FactoryB implements Factory {
@Override
public Product create() {
return new ProductB();
}
}
public class Main {
public static void main(String[] args) {
Factory factory = new FactoryA();
factory.create().use();
}
}框架源码中的它
- JDK:
URL#openConnection()由具体协议(http/file...)决定返回哪种URLConnection; - JDK:
Collection#iterator(),ArrayList 与 LinkedList 各自返回自己的迭代器实现; - Spring:
FactoryBean#getObject(),MyBatis 的 SqlSessionFactoryBean 就是靠它把"造 SqlSessionFactory"交给容器。
建造者模式
高频 十个可选参数的构造器怎么写都难看:重叠构造器一层套一层,调用方传参全靠数逗号。建造者的解法是把构造过程拆成流式装配:必填参数前置,可选项链式设置,最后一次 build() 交付不可变对象。public class HttpRequest {
// final 保证 build 之后不可变;必填参数由构造器强制
private final String url;
private final String method;
private final Map<String, String> headers;
private final String body;
private final int timeout;
private HttpRequest(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers;
this.body = builder.body;
this.timeout = builder.timeout;
}
public static Builder builder(String url) {
return new Builder(url);
}
// 建造者:链式装配,build 一次性交付
public static class Builder {
private final String url;
private String method = "GET";
private final Map<String, String> headers = new HashMap<>();
private String body;
private int timeout = 3000;
public Builder(String url) {
this.url = url;
}
public Builder method(String method) {
this.method = method;
return this;
}
public Builder header(String key, String value) {
headers.put(key, value);
return this;
}
public Builder body(String body) {
this.body = body;
return this;
}
public Builder timeout(int timeout) {
this.timeout = timeout;
return this;
}
public HttpRequest build() {
return new HttpRequest(this);
}
}
}
public class Main {
public static void main(String[] args) {
HttpRequest request = HttpRequest.builder("https://victor.spring-cloud.cn")
.method("POST")
.header("Content-Type", "application/json")
.body("{}")
.timeout(5000)
.build();
}
}与工厂的区别一句话:工厂关心"造哪一种",建造者关心"怎么一步步装配"。
框架源码中的它
- JDK 11:
HttpRequest.newBuilder(URI.create("...")).header(...).build(),官方 API 亲自示范; - Lombok 的
@Builder注解一键生成上述样板代码; StringBuilder、Stream.builder()是它的简化形态:分步追加,一次成器。
抽象工厂模式
工厂方法一次造一件产品;当产品升级为一族配套件——Windows 风格的按钮要配 Windows 风格的输入框,换成 Mac 必须整套换、不许混搭——就需要抽象工厂:一族产品一个工厂,成套创建。
// 产品族中的两个抽象产品
public interface Button {
void render();
}
public interface Input {
void render();
}
// Windows 一族
public class WinButton implements Button {
@Override
public void render() {
System.out.println("渲染 Windows 风格按钮");
}
}
public class WinInput implements Input {
@Override
public void render() {
System.out.println("渲染 Windows 风格输入框");
}
}
// 抽象工厂:一族产品对应一个工厂
public interface UiFactory {
Button createButton();
Input createInput();
}
public class WinUiFactory implements UiFactory {
@Override
public Button createButton() {
return new WinButton();
}
@Override
public Input createInput() {
return new WinInput();
}
}
public class Main {
public static void main(String[] args) {
UiFactory factory = new WinUiFactory();
factory.createButton().render();
factory.createInput().render();
}
}缺点也要说透:产品族里新增一种产品(比如再加个 ScrollBar),所有工厂接口和实现都要改——抽象工厂优化的是"整套换族",对"族内加品类"并不友好。
框架源码中的它
- JDK:
java.sql.Connection就是抽象工厂,createStatement()、prepareStatement()成套产出同一方言族的产品; - JDK:
DocumentBuilderFactory、TransformerFactory同理。
原型模式
有些对象"生下来"就很贵——要查库、要计算、要装配一大屏数据。若新对象和现有对象只差几个字段,重新造一遍纯属浪费:以现有对象为模板克隆一份,再改差异点。
public class Report implements Cloneable {
private String title;
// 引用类型字段:浅拷贝后两个对象共享同一个 list,改一个另一个也变
private List<String> chapters = new ArrayList<>();
@Override
public Report clone() {
try {
return (Report) super.clone();
} catch (CloneNotSupportedException e) {
// 实现了 Cloneable,理论上不会到这里
throw new AssertionError(e);
}
}
// 深拷贝:引用字段各自再复制一份,两对象彻底独立
public Report deepClone() {
Report copy = this.clone();
copy.chapters = new ArrayList<>(this.chapters);
return copy;
}
}浅拷贝与深拷贝是本模式唯一的坑:Object#clone() 只复制"值",引用字段仍指向同一个对象——克隆报表后往章节里加内容,原件也跟着变了。嵌套层级深时,用序列化/反序列化实现深拷贝更省心(如 Hutool 的 ObjectUtil.cloneByStream)。
框架源码中的它
- JDK:
Object#clone()配合Cloneable——一个没有任何方法的"标记接口",仅用于给 JVM 打标记; - JDK:
ArrayList#clone()就是浅拷贝,克隆后修改内部元素,原 list 同步可见,可自行验证。
结构型模式
适配器模式
高频 新系统要用的接口,旧服务对不上——不改两边代码,中间加一层"转接头"做翻译,旧服务继续服役。// 目标接口:系统期望的新接口
public interface MessageSender {
void send(String target, String content);
}
// 已有的旧服务:参数格式与新接口不兼容
public class OldSmsService {
public void push(String targetAndContent) {
System.out.println("短信已发送: " + targetAndContent);
}
}
// 适配器:组合旧服务,把新接口"翻译"成旧调用
public class SmsAdapter implements MessageSender {
private final OldSmsService oldService = new OldSmsService();
@Override
public void send(String target, String content) {
// 接口翻译:两个参数拼成旧服务认识的一个参数
oldService.push(target + ":" + content);
}
}
public class Main {
public static void main(String[] args) {
// 调用方只认新接口,感知不到旧服务的存在
MessageSender sender = new SmsAdapter();
sender.send("138xxxx0000", "您的快递已到菜鸟驿站");
}
}框架源码中的它
- JDK:
InputStreamReader是字节流到字符流的适配器,Reader读的其实是InputStream翻译后的结果; - Spring MVC:
HandlerAdapter——注解 Controller、函数式端点等不同形态,靠它适配成统一的调用方式,源码级高频。
装饰器模式
高频 不改原类、不用继承,通过层层包装动态叠加功能——俄罗斯套娃,每包一层加一点料,核心是"装饰器和被装饰者实现同一个接口"。// 组件接口:饮料与各种加料共同的样子
public interface Coffee {
double cost();
String desc();
}
// 被装饰的原始对象
public class Americano implements Coffee {
@Override
public double cost() {
return 15;
}
@Override
public String desc() {
return "美式";
}
}
// 装饰器基类:持有一个 Coffee,自己也是 Coffee
public abstract class CoffeeDecorator implements Coffee {
protected final Coffee coffee;
protected CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
// 具体装饰器:加奶
public class Milk extends CoffeeDecorator {
public Milk(Coffee coffee) {
super(coffee);
}
@Override
public double cost() {
return coffee.cost() + 3;
}
@Override
public String desc() {
return coffee.desc() + "+奶";
}
}
// 具体装饰器:加糖
public class Sugar extends CoffeeDecorator {
public Sugar(Coffee coffee) {
super(coffee);
}
@Override
public double cost() {
return coffee.cost() + 1;
}
@Override
public String desc() {
return coffee.desc() + "+糖";
}
}
public class Main {
public static void main(String[] args) {
// 一层层包上去,价格由最外层逐层向内累加
Coffee order = new Sugar(new Milk(new Americano()));
System.out.println(order.desc() + " = " + order.cost() + " 元");
}
}框架源码中的它
- JDK IO 全家桶:
new BufferedReader(new InputStreamReader(System.in))就是装饰器套娃,一层管缓冲、一层管编码; - Spring:
HttpServletRequestWrapper、ConnectionWrapper一族; - 与代理的区别一句话:装饰器重在加功能(同接口多层包装),代理重在控制访问。
代理模式
高频 不直接访问目标对象,通过一个"中间人"控制访问——权限校验、日志、缓存都做在中间人身上,目标对象毫发无损。// 目标接口与实现
public interface UserService {
void save(String name);
}
public class UserServiceImpl implements UserService {
@Override
public void save(String name) {
System.out.println("保存用户: " + name);
}
}
// 静态代理:与目标实现同一接口,前后织入附加逻辑
public class UserServiceProxy implements UserService {
private final UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void save(String name) {
System.out.println("[日志] save 开始");
target.save(name);
System.out.println("[日志] save 结束");
}
}静态代理每个接口都要手写一个代理类,受不了;JDK 动态代理在运行时生成代理类,一个处理器服务所有接口:
// 动态代理处理器:所有方法调用都会进入 invoke
public class LogHandler implements InvocationHandler {
private final Object target;
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[日志] " + method.getName() + " 开始");
Object result = method.invoke(target, args);
System.out.println("[日志] " + method.getName() + " 结束");
return result;
}
}
public class Main {
public static void main(String[] args) {
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
new LogHandler(new UserServiceImpl()));
proxy.save("zjx");
}
}框架源码中的它
- Spring AOP 的底层:目标有接口走 JDK 动态代理,无接口走 CGLIB 子类化——面试高频;
- MyBatis:Mapper 接口没有实现类却能被调用,靠的正是动态代理。
外观模式
高频 子系统内部盘根错节,给调用方一个统一的"总开关":门面一个方法,封装整条流程,调用方不必认识任何一个子系统。// 复杂的子系统(各自独立、互相配合)
public class StockService {
public void deduct(String sku) {
System.out.println("扣减库存: " + sku);
}
}
public class PayService {
public void pay(String orderId, double amount) {
System.out.println("支付订单: " + orderId + " 金额: " + amount);
}
}
public class LogisticsService {
public void ship(String orderId) {
System.out.println("发货: " + orderId);
}
}
// 外观:一个方法封装整个下单流程
public class OrderFacade {
private final StockService stock = new StockService();
private final PayService pay = new PayService();
private final LogisticsService logistics = new LogisticsService();
public void placeOrder(String sku, String orderId, double amount) {
stock.deduct(sku);
pay.pay(orderId, amount);
logistics.ship(orderId);
}
}
public class Main {
public static void main(String[] args) {
// 调用方一行搞定,无需认识三个子系统
new OrderFacade().placeOrder("SKU-001", "ORD-1001", 299);
}
}框架源码中的它
- SLF4J 本身就是日志门面,背后 Logback/Log4j2 随意切换,业务代码零改动;
- Spring 的
JdbcTemplate、TransactionTemplate都是门面——把 JDBC 的繁琐操作包成一个简单入口。
组合模式
树形结构的"部分-整体"问题:叶子和容器实现同一接口,容器递归委托给子节点,调用方无需 if 判断"这是单个还是一串"。
// 组件:文件与文件夹的统一抽象
public abstract class Node {
protected final String name;
protected Node(String name) {
this.name = name;
}
public abstract long size();
public abstract void print(String indent);
}
// 叶子:文件
public class FileNode extends Node {
private final long size;
public FileNode(String name, long size) {
super(name);
this.size = size;
}
@Override
public long size() {
return size;
}
@Override
public void print(String indent) {
System.out.println(indent + "文件 " + name + " (" + size + "B)");
}
}
// 容器:文件夹,持有子节点集合
public class FolderNode extends Node {
private final List<Node> children = new ArrayList<>();
public FolderNode(String name) {
super(name);
}
public void add(Node node) {
children.add(node);
}
@Override
public long size() {
// 关键:容器递归求和,调用方对叶子和容器一视同仁
return children.stream().mapToLong(Node::size).sum();
}
@Override
public void print(String indent) {
System.out.println(indent + "目录 " + name);
children.forEach(child -> child.print(indent + " "));
}
}
public class Main {
public static void main(String[] args) {
FolderNode root = new FolderNode("project");
root.add(new FileNode("pom.xml", 2048));
FolderNode src = new FolderNode("src");
src.add(new FileNode("Main.java", 1024));
root.add(src);
// 求大小、打印树,全程无需判断节点类型
System.out.println("总大小: " + root.size() + "B");
root.print("");
}
}框架源码中的它
- AWT/Swing:
Container#add(Component),容器与组件同根,可无限嵌套; - MyBatis:
MixedSqlNode组合各种 SQL 节点拼出动态 SQL。
桥接模式
两个维度同时变化时,继承会让子类数量爆炸(2×2=4、3×3=9)。桥接把两个维度拆成两棵独立的继承树,用组合连接——各自扩展,互不牵连。
// 实现维度:发送通道
public interface Channel {
void send(String message);
}
public class EmailChannel implements Channel {
@Override
public void send(String message) {
System.out.println("[邮件] " + message);
}
}
public class SmsChannel implements Channel {
@Override
public void send(String message) {
System.out.println("[短信] " + message);
}
}
// 功能维度:消息类型,持有通道引用——"桥"就在这个组合上
public abstract class Notification {
protected final Channel channel;
protected Notification(Channel channel) {
this.channel = channel;
}
public abstract void notifyUser(String content);
}
public class NormalNotification extends Notification {
public NormalNotification(Channel channel) {
super(channel);
}
@Override
public void notifyUser(String content) {
channel.send("普通通知: " + content);
}
}
public class UrgentNotification extends Notification {
public UrgentNotification(Channel channel) {
super(channel);
}
@Override
public void notifyUser(String content) {
channel.send("【加急】" + content);
}
}
public class Main {
public static void main(String[] args) {
// 2 种类型 × 2 种通道自由组合,无需 4 个子类
new UrgentNotification(new SmsChannel()).notifyUser("服务器 CPU 95%");
new NormalNotification(new EmailChannel()).notifyUser("周报提醒");
}
}框架源码中的它
- JDBC:
DriverManager(API 一侧)与各数据库Driver(实现一侧)分离,换驱动不换代码; - SLF4J(抽象)与 Logback(实现)分离,也是桥接思想。
享元模式
海量细粒度对象吃内存时,把可共享的内部状态缓存复用,不可共享的外部状态由调用方临时传入——一万个棋子,只有两种颜色对象。
// 享元对象:内部状态(颜色)不可变、可共享
public class ChessPiece {
private final String color;
ChessPiece(String color) {
this.color = color;
}
// 外部状态(位置)由调用方传入,不存进对象
public void place(int x, int y) {
System.out.println(color + "棋落在(" + x + "," + y + ")");
}
}
// 享元工厂:同色棋子全局只创建一次
public class ChessFactory {
private static final Map<String, ChessPiece> CACHE = new HashMap<>();
public static ChessPiece get(String color) {
return CACHE.computeIfAbsent(color, ChessPiece::new);
}
}框架源码中的它
- JDK:
Integer.valueOf()缓存 -128~127、字符串常量池——最古老的享元,天天在用而浑然不觉; - 数据库连接池、线程池复用重对象,是享元思想的工程化放大。
行为型模式
策略模式
高频 一族算法各自封装、可互相替换,上下文只管委托——消灭 if-else 分支的利器。public class PaymentService {
public void pay(String paymentType, double amount) {
if ("ALIPAY".equals(paymentType)) {
// 支付宝支付逻辑
System.out.println("使用支付宝支付: " + amount);
// 复杂的支付宝逻辑...
} else if ("WECHAT".equals(paymentType)) {
// 微信支付逻辑
System.out.println("使用微信支付: " + amount);
// 复杂的微信支付逻辑...
} else if ("CREDIT_CARD".equals(paymentType)) {
// 信用卡支付逻辑
System.out.println("使用信用卡支付: " + amount);
// 复杂的信用卡逻辑...
} else {
throw new IllegalArgumentException("不支持的支付方式");
}
}
}// 1. 策略接口
public interface PaymentStrategy {
void pay(double amount);
String getType();
}
// 2. 具体策略实现
@Component
public class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用支付宝支付: " + amount);
// 具体的支付宝支付逻辑
}
@Override
public String getType() {
return "ALIPAY";
}
}
@Component
public class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用微信支付: " + amount);
// 具体的微信支付逻辑
}
@Override
public String getType() {
return "WECHAT";
}
}
// 3. 策略上下文(使用Spring管理)
@Service
public class PaymentContext {
private final Map<String, PaymentStrategy> strategyMap = new ConcurrentHashMap<>();
// 注入所有策略实现
public PaymentContext(List<PaymentStrategy> strategies) {
for (PaymentStrategy strategy : strategies) {
strategyMap.put(strategy.getType(), strategy);
}
}
public void pay(String paymentType, double amount) {
PaymentStrategy strategy = strategyMap.get(paymentType);
if (strategy == null) {
throw new IllegalArgumentException("不支持的支付方式: " + paymentType);
}
strategy.pay(amount);
}
}
// 4. 使用方式
@RestController
public class PaymentController {
@Autowired
private PaymentContext paymentContext;
@PostMapping("/pay")
public void pay(@RequestParam String type, @RequestParam double amount) {
// 无需 if-else 判断
paymentContext.pay(type, amount);
}
}框架源码中的它
- JDK:
Comparator——Arrays.sort流程不变,比较策略随时替换; - JDK:线程池的四种拒绝策略(AbortPolicy / CallerRunsPolicy / DiscardPolicy / DiscardOldestPolicy),面试高频;
- Spring:注入
List<策略接口>自动收集全部实现,正是上文 PaymentContext 的玩法。
观察者模式
高频 一对多依赖:被观察者状态一变,所有订阅者自动收到通知。发布-订阅是它的别名,所有消息队列都是它的分布式放大版。// 观察者接口(函数式接口,可用 Lambda 订阅)
public interface Subscriber {
void onArticle(String title);
}
// 被观察者:维护订阅者列表,状态变化时逐个通知
public class WechatAccount {
private final List<Subscriber> subscribers = new ArrayList<>();
public void subscribe(Subscriber subscriber) {
subscribers.add(subscriber);
}
public void unsubscribe(Subscriber subscriber) {
subscribers.remove(subscriber);
}
public void publish(String title) {
System.out.println("公众号发布新文章: " + title);
// "推"模式:内容直接推给每个订阅者
subscribers.forEach(s -> s.onArticle(title));
}
}
public class Main {
public static void main(String[] args) {
WechatAccount account = new WechatAccount();
account.subscribe(title -> System.out.println("读者A收到: " + title));
account.subscribe(title -> System.out.println("读者B收到: " + title));
account.publish("设计模式完结篇");
}
}框架源码中的它
- Spring:
ApplicationEventPublisher+@EventListener,业务解耦神器; - JDK:AWT/Swing 的事件监听(
addActionListener)、Guava 的 EventBus; - ZooKeeper 的 Watcher、所有 MQ 的思想源头。
责任链模式
高频 请求沿着处理器链一路传递,每个处理器自己决定"处理掉"还是"传给下一个"——审批流、过滤器、拦截器的天然形态。// 请求对象
public class Request {
private final String token;
public Request(String token) {
this.token = token;
}
public String getToken() {
return token;
}
}
// 处理器基类:持有下一个节点
public abstract class Handler {
protected Handler next;
// 组链的链式写法,返回下一个节点便于继续串联
public Handler next(Handler next) {
this.next = next;
return next;
}
public abstract void handle(Request request);
}
// 具体处理器1:鉴权
public class AuthHandler extends Handler {
@Override
public void handle(Request request) {
if (request.getToken() == null) {
System.out.println("拦截: 未登录");
// 处理掉(拒绝),链条到此为止
return;
}
System.out.println("鉴权通过");
if (next != null) {
next.handle(request);
}
}
}
// 具体处理器2:限流
public class RateLimitHandler extends Handler {
@Override
public void handle(Request request) {
System.out.println("限流检查通过");
if (next != null) {
next.handle(request);
}
}
}
// 具体处理器3:业务处理(链的末端)
public class BusinessHandler extends Handler {
@Override
public void handle(Request request) {
System.out.println("执行业务: " + request.getToken());
}
}
public class Main {
public static void main(String[] args) {
// 组链:鉴权 -> 限流 -> 业务
Handler chain = new AuthHandler();
chain.next(new RateLimitHandler()).next(new BusinessHandler());
chain.handle(new Request("zjx-token"));
}
}框架源码中的它
- Servlet 的
Filter/FilterChain、Spring MVC 的Interceptor; - Netty 的
ChannelPipeline、OkHttp 的 Interceptor、Sentinel 的 Slot 链——Web 框架的半壁江山都是它。
模板方法模式
高频 准备一个抽象类,将部分逻辑实现后,再声明一些抽象方法迫使子类实现剩余逻辑,不同的子类可实现不同逻辑。模板方法模式的关键在于:父类提供框架性的公共逻辑,子类提供个性化的定制逻辑。
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
// 子类A
TemplateDemo action = new ActionA();
// 执行基类的模板方法
action.tempMethod();
}
static abstract class TemplateDemo {
// 模板方法,逻辑骨架
public void tempMethod() {
System.out.println("模板方法的算法骨架被执行");
// 执行前的公共操作
beforeAction();
// 调用钩子方法
action();
// 执行后的公共操作
afterAction();
}
// 执行前的公共逻辑;final 修饰防止子类重写破坏模板流程
protected final void beforeAction() {
System.out.println("准备执行钩子方法");
}
// 钩子方法:定义为抽象方法,强制子类实现
public abstract void action();
// 执行后的公共逻辑
protected final void afterAction() {
System.out.println("钩子方法执行完成");
}
}
// 子类A:提供了钩子方法实现,不同子类可自定义不同钩子逻辑。
static class ActionA extends TemplateDemo {
@Override
public void action() {
System.out.println("钩子方法的实现 test.ActionA.action() 被执行");
}
}
}
:::tip 框架源码中的它
- AQS:加锁、排队、唤醒的流程固定在父类,子类只需实现 tryAcquire/tryRelease——教科书级的模板方法;
- JDK:`HttpServlet#service()` 分发逻辑写死,doGet/doPost 留给子类;
- Spring:`JdbcTemplate` 把连接管理、异常转换、资源释放写死在模板里,回调只管业务 SQL。
:::
### 命令模式
把"请求"本身封装成对象——于是请求可以**排队执行、记录历史、一键撤销**。调用者只认识命令对象,不认识具体执行者。
```java
// 命令接口:把"做什么"封装成对象,天然支持撤销
public interface Command {
void execute();
void undo();
}
// 接收者:真正干活的设备
public class Light {
public void on() {
System.out.println("开灯");
}
public void off() {
System.out.println("关灯");
}
}
// 具体命令:开灯(撤销自然就是关灯)
public class LightOnCommand implements Command {
private final Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
// 调用者:只操作命令对象,并用栈记录历史以支持撤销
public class RemoteControl {
private final Deque<Command> history = new ArrayDeque<>();
public void execute(Command command) {
command.execute();
history.push(command);
}
public void undo() {
Command command = history.poll();
if (command != null) {
command.undo();
}
}
}
public class Main {
public static void main(String[] args) {
RemoteControl remote = new RemoteControl();
Light light = new Light();
remote.execute(new LightOnCommand(light));
remote.undo();
}
}框架源码中的它
- JDK:
Runnable就是最简命令模式,线程池队列里排的全是命令对象; - 编辑器的 Ctrl+Z 撤销栈、游戏的回放系统,都是命令对象的历史记录。
状态模式
状态决定行为:把每个 if/switch 分支里的状态逻辑各自封装成类,状态切换 = 换一个状态对象。它和策略是孪生兄弟——策略由外部选定后不变,状态由对象内部按规则自动流转。
// 状态接口:每种状态自己定义"名称"与"下一步"
public interface OrderState {
String name();
OrderState next();
}
public class PaidState implements OrderState {
@Override
public String name() {
return "已支付";
}
@Override
public OrderState next() {
return new ShippedState();
}
}
public class ShippedState implements OrderState {
@Override
public String name() {
return "已发货";
}
@Override
public OrderState next() {
return new ReceivedState();
}
}
public class ReceivedState implements OrderState {
@Override
public String name() {
return "已签收";
}
@Override
public OrderState next() {
// 终态:流转到自己
return this;
}
}
// 上下文:持有当前状态,行为全部委托给状态对象
public class Order {
private OrderState state = new PaidState();
public void nextState() {
state = state.next();
System.out.println("订单状态: " + state.name());
}
}
public class Main {
public static void main(String[] args) {
Order order = new Order();
order.nextState();
order.nextState();
order.nextState();
}
}框架源码中的它
- Spring 生态的 Spring StateMachine 框架;
- JDK:
Thread.State(NEW/RUNNABLE/BLOCKED...)的状态流转,源码级的教科书。
迭代器模式
把"怎么遍历"封装成迭代器对象,调用方只管 hasNext()/next()——集合内部是数组、链表还是树,被彻底隐藏。
// 自定义容器实现 Iterable,即可享受 for-each 语法糖
public class BookShelf implements Iterable<String> {
private final String[] books = {"Java", "JVM", "MySQL"};
@Override
public Iterator<String> iterator() {
return new Iterator<>() {
private int cursor;
@Override
public boolean hasNext() {
return cursor < books.length;
}
@Override
public String next() {
return books[cursor++];
}
};
}
}
public class Main {
public static void main(String[] args) {
for (String book : new BookShelf()) {
System.out.println(book);
}
}
}框架源码中的它
- JDK:for-each 的幕后就是
Iterable/Iterator,Collection接口继承自Iterable; - MyBatis:游标 Cursor 惰性逐行读取,避免大结果集撑爆内存。
低频行为型速查
| 模式 | 一句话本质 | 典型出处 |
|---|---|---|
| 中介者 | 网状依赖收拢为星型,同类对象交互统一经过中介者转发 | 航空调度塔台;MVC 中的 Controller |
| 备忘录 | 存档/读档——把对象状态快照存起来,支持随时回滚 | 游戏存档;编辑器撤销;事务回滚 |
| 访问者 | 不改动类的前提下为其新增"操作",双分派按类型分发到不同访问方法 | ASM 字节码遍历;文件树统计 |
| 解释器 | 为某一特定语法定义表示和解释器,逐条解释执行 | 正则引擎;SpEL 表达式;SQL 解析 |
其他经典模式
生产者-消费者模式
高频 生产者线程(若干个)向数据缓冲区(DataBuffer)加入数据,消费者线程(若干个)从数据缓冲区消耗数据,需保证公用数据线程安全问题。“生产者-消费者”模式是一个经典的多线程设计模式。class Producer implements Runnable {
private final BlockingQueue<Integer> queue;
Producer(BlockingQueue<Integer> q) {
queue = q;
}
public void run() {
try {
for (int i = 0; i < 10; i++) {
queue.put(produce());
System.out.println("Produced: " + i);
Thread.sleep(1000);
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
private int produce() {
return (int) (Math.random() * 100);
}
}
class Consumer implements Runnable {
private final BlockingQueue<Integer> queue;
Consumer(BlockingQueue<Integer> q) {
queue = q;
}
public void run() {
try {
while (true) {
consume(queue.take());
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
private void consume(Integer x) {
System.out.println("Consumed: " + x);
}
}
public class ProducerConsumerExample {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
Producer producer = new Producer(queue);
Consumer consumer = new Consumer(queue);
Thread producerThread = new Thread(producer);
Thread consumerThread = new Thread(consumer);
producerThread.start();
consumerThread.start();
}
}框架源码中的它
- JDK:线程池本身就是生产者-消费者——
submit()把任务投进阻塞队列,工作线程循环take()消费; - 中间件:Kafka/RocketMQ 本质是分布式的生产者-消费者,缓冲区从内存队列放大成了消息队列。

