前言

设计模式(Design Patterns)是软件工程中反复出现的典型问题的通用解决方案。GoF(Gang of Four)在 1994 年出版的《设计模式:可复用面向对象软件的基础》一书,定义了 23 种经典设计模式。近三十年过去,这些模式不仅在 Java 生态中扎下了根,更在 Spring 等主流框架中得到了广泛而深刻的体现。本文将结合实际代码和框架源码,梳理设计模式在 Java 中的经典应用。

为什么 Java 开发者必须掌握设计模式

Java 语言本身就是一门高度面向对象的语言,其标准库和第三方框架大量运用设计模式。理解设计模式意味着:

  1. 读懂框架源码:Spring、MyBatis、Netty 的源码中随处可见工厂、代理、模板方法等模式。
  2. 写出可维护的代码:设计模式提供了应对变化的词汇表和蓝图。
  3. 在团队中高效沟通:”这里用策略模式”比”这里用 if-else 判断然后调用不同方法”更精准。

创建型模式

单例模式(Singleton)

单例模式确保一个类只有一个实例,并提供全局访问点。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
// 方式一:饿汉式(线程安全,但可能浪费资源)
public class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() { return INSTANCE; }
}

// 方式二:双重检查锁定(JDK 5+,volatile 避免指令重排)
public class DclSingleton {
private static volatile DclSingleton instance;
private DclSingleton() {}
public static DclSingleton getInstance() {
if (instance == null) {
synchronized (DclSingleton.class) {
if (instance == null) {
instance = new DclSingleton();
}
}
}
return instance;
}
}

// 方式三:枚举实现(《Effective Java》推荐,防反射、防序列化破坏)
public enum EnumSingleton {
INSTANCE;

public void doSomething() {
// 业务逻辑
}
}

枚举单例是 Joshua Bloch 推荐的最佳实践。它天然提供序列化安全性和反射攻击防护,代码也最为简洁。

工厂方法与抽象工厂

工厂方法将对象的创建延迟到子类,而抽象工厂提供一个创建一系列相关对象的接口。

1
2
3
4
5
6
7
8
9
10
11
// 工厂方法 —— JDK 中的 Calendar.getInstance()
Calendar calendar = Calendar.getInstance(); // 根据 Locale 返回不同的 Calendar 实现

// 抽象工厂 —— 连接不同数据库
public interface ConnectionFactory {
Connection createConnection();
Statement createStatement();
}

public class MySQLFactory implements ConnectionFactory { /* ... */ }
public class PostgreSQLFactory implements ConnectionFactory { /* ... */ }

在 Spring 框架中,BeanFactory 本身就是工厂模式的典范。它通过 getBean() 方法创建和管理 Bean,将对象的创建与使用分离。

建造者模式(Builder)

建造者模式将复杂对象的构造过程与表示分离。Lombok 的 @Builder 注解让建造者模式在 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
// Lombok @Builder 的本质等价于手写以下代码
public class User {
private final String name;
private final int age;
private final String email;

private User(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.email = builder.email;
}

public static class Builder {
private String name;
private int age;
private String email;

public Builder name(String name) { this.name = name; return this; }
public Builder age(int age) { this.age = age; return this; }
public Builder email(String email) { this.email = email; return this; }
public User build() { return new User(this); }
}
}

// 使用
User user = new User.Builder()
.name("张三")
.age(25)
.email("zhangsan@example.com")
.build();

建造者模式特别适合参数多、有必选和可选之分的场景。相比重叠构造器(Telescoping Constructor),Builder 的可读性和灵活性都远胜一筹。

结构型模式

适配器模式(Adapter)

适配器模式将一个接口转换为客户期望的另一个接口,让原本不兼容的类能够合作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 场景:旧系统使用 OldLogger 接口,新模块需要统一的 Logger 接口
interface Logger {
void log(String msg);
}

class OldLogger {
void writeLog(String msg) { /* ... */ }
}

// 适配器
class LoggerAdapter implements Logger {
private final OldLogger oldLogger;

public LoggerAdapter(OldLogger oldLogger) {
this.oldLogger = oldLogger;
}

@Override
public void log(String msg) {
oldLogger.writeLog(msg); // 接口转换
}
}

在 JDK 中,Arrays.asList() 就是一个适配器——它将数组适配为 List 接口。

装饰器模式(Decorator)

装饰器模式动态地为对象添加额外职责。Java IO 流是装饰器模式最经典的案例:

1
2
3
4
5
6
7
8
9
// 层层装饰,灵活组合功能
InputStream raw = new FileInputStream("data.txt"); // 基础组件
InputStream buffered = new BufferedInputStream(raw); // 增加缓冲
InputStream dataStream = new DataInputStream(buffered); // 增加数据读取能力

// 还可以加上解压
InputStream compressed = new GZIPInputStream(
new BufferedInputStream(new FileInputStream("data.gz"))
);

每一层装饰器都实现了 InputStream 接口,并在内部持有一个 InputStream 引用,形成一条装饰链。这种设计让你可以在运行时自由组合功能,而不需要创建无数个子类。

代理模式(Proxy)与 JDK 动态代理

代理模式为另一个对象提供一个替身或占位符来控制对原对象的访问。Java 的动态代理机制让代理模式在 AOP(面向切面编程)中大放异彩。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// JDK 动态代理示例
public class LoggingInvocationHandler implements InvocationHandler {
private final Object target;

public LoggingInvocationHandler(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;
}
}

// 创建代理
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class<?>[]{UserService.class},
new LoggingInvocationHandler(new UserServiceImpl())
);

Spring AOP 就是基于 JDK 动态代理和 CGLIB 代理构建的。当目标类实现了接口时,Spring 使用 JDK 动态代理;否则使用 CGLIB 生成子类代理。

外观模式(Facade)

外观模式为子系统中的一组接口提供一个统一的入口。典型的例子是 SLF4J 日志门面:它为 Log4j、Logback、java.util.logging 等不同的日志框架提供统一的 API,让使用者在切换日志框架时无需修改业务代码。

1
2
3
4
5
6
7
8
9
10
11
// 业务代码只依赖 SLF4J 的接口
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);

public void createOrder(Order order) {
log.info("创建订单: {}", order.getId());
}
}

组合模式(Composite)

组合模式将对象组织成树形结构,以表示”部分-整体”的层次关系,让客户端可以一致地对待单个对象和组合对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface FileSystemNode {
void showInfo();
}

class File implements FileSystemNode {
private String name;
public void showInfo() { System.out.println("文件: " + name); }
}

class Directory implements FileSystemNode {
private String name;
private List<FileSystemNode> children = new ArrayList<>();

public void add(FileSystemNode node) { children.add(node); }

public void showInfo() {
System.out.println("目录: " + name);
for (FileSystemNode child : children) {
child.showInfo();
}
}
}

行为型模式

观察者模式(Observer)

观察者模式定义了对象之间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知。

在 Java 中,java.beans.PropertyChangeListener 提供了观察者模式的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class OrderModel {
private String status;
private final PropertyChangeSupport support = new PropertyChangeSupport(this);

public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}

public void setStatus(String newStatus) {
String oldStatus = this.status;
this.status = newStatus;
support.firePropertyChange("status", oldStatus, newStatus);
}
}

策略模式(Strategy)

策略模式定义一系列算法,把它们封装起来,使它们可以互换。Comparator 是 JDK 中最直观的策略模式体现:

1
2
3
4
5
6
7
8
// 不同的排序策略
Comparator<Person> byAge = (a, b) -> Integer.compare(a.getAge(), b.getAge());
Comparator<Person> byName = (a, b) -> a.getName().compareTo(b.getName());

// 运行时选择策略
List<Person> people = loadPeople();
people.sort(byAge); // 按年龄排序
people.sort(byName); // 按姓名排序

模板方法模式(Template Method)

模板方法在父类中定义算法的骨架,将某些步骤延迟到子类实现。Spring 的 JdbcTemplate 就是这一模式的经典应用:

1
2
3
4
5
6
7
8
9
10
11
// JdbcTemplate 的 query 方法是模板方法
// 你只需提供 RowMapper(勾子方法)
List<User> users = jdbcTemplate.query(
"SELECT * FROM users WHERE age > ?",
(rs, rowNum) -> new User(
rs.getLong("id"),
rs.getString("name"),
rs.getInt("age")
),
18
);

模板方法帮我们解决了连接管理、异常处理和资源释放等重复性工作,我们只需关注 SQL 和结果映射。

责任链模式(Chain of Responsibility)

责任链模式将请求发送者与接收者解耦,让多个对象都有机会处理请求。Servlet Filter 是责任链的典型实现:

1
2
3
4
5
6
7
8
9
10
11
// 每个过滤器处理一部分职责,然后交给链上的下一个
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
if (authenticate(req)) {
chain.doFilter(req, resp); // 放行
} else {
((HttpServletResponse) resp).sendError(401);
}
}
}

命令模式(Command)

命令模式将请求封装为对象,从而支持请求的参数化、队列化、日志化和撤销操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Command {
void execute();
}

class SaveCommand implements Command {
private final Editor editor;
public void execute() { editor.save(); }
}

class UndoCommand implements Command {
private final Editor editor;
public void execute() { editor.undo(); }
}

在 Spring 中,Runnable 被大量用来封装命令,配合 TaskExecutor 实现异步执行。

设计模式在 Spring 框架中的体现

Spring 框架本身就是一本活的设计模式教科书。以下是对 Spring 中关键模式的总结:

设计模式 Spring 中的应用 说明
工厂方法 + 策略 IoC 容器(BeanFactory, ApplicationContext) 对象的创建与管理完全由容器负责
代理模式 AOP(JDK 动态代理 / CGLIB) 横切关注点(事务、日志、安全)的无侵入织入
模板方法 JdbcTemplate, RestTemplate, KafkaTemplate 封装样板代码,暴露扩展点
前端控制器 DispatcherServlet 所有 HTTP 请求的统一入口
观察者模式 ApplicationEvent, @EventListener 组件间解耦通信
单例模式 Bean 的默认作用域(singleton) 每个 Bean 定义只有一个实例
适配器模式 HandlerAdapter 将不同类型的 Controller 适配为统一的处理流程

什么时候不应该使用设计模式

设计模式不是越多越好。过度使用设计模式会导致以下问题:

  1. 代码膨胀:为未来的”可能变化”引入过多的抽象层,让简单问题变得复杂。
  2. 可读性下降:一个简单的业务逻辑被拆成五六层间接调用,调试困难。
  3. 性能开销:每次方法调用都经过层层代理和适配,尤其在热路径上影响显著。

一个实用的判断标准是Rule of Three:当同一个变化发生三次时,再引入抽象和模式;在此之前,保持简单。遵循 YAGNI(You Ain’t Gonna Need It),不要为不会发生的需求提前设计。

总结

设计模式是解决特定问题的工具箱,不是教条。在 Java 生态中,从 JDK 标准库到 Spring Framework,设计模式无处不在。理解它们,可以让你更好地阅读框架源码、设计灵活的系统架构。但记住:模式服务于代码,而非代码服务于模式。选择模式时,永远以”让代码更清晰、更易维护”为标准。