Java 工程师必会的设计模式:从策略模式到模板方法
一、为什么学设计模式
学设计模式不是为了面试背题,而是积累经过验证的解决方案。
你遇到的问题,前人早就遇到过。前人把解法命名、归档,形成了设计模式。掌握这些模式,遇到问题时就能站在巨人的肩膀上,而不是从零思考。
Java JDK 本身就用了大量设计模式。读 JDK 源码时认出设计模式,能帮你更快理解它的设计逻辑。
这篇文章不会讲 23 种模式,挑最实用的 7 种,配合 JDK 里的实际用法。
二、单例模式(Singleton)
解决的问题:一个类只应该有一个实例,全局唯一。
常见写法:双重检查锁定
1 | public class Singleton { |
volatile 防止指令重排序,确保多线程安全。JDK 里的 java.lang.Runtime 就是单例 —— 每个 JVM 只有一个 Runtime 实例。
为什么不用全局变量
全局变量也能做到全局唯一,但无法防止有人 new Singleton() 创建第二个实例。单例模式把构造函数设为 private,从语言层面保证了唯一性。
三、工厂模式(Factory)
解决的问题:对象创建逻辑和使用逻辑分离,切换实现不需要改调用方代码。
简单工厂
1 | public class NotificationFactory { |
调用方只依赖 Notification 接口,不需要知道具体是哪种通知。
JDK 源码里的工厂
java.time.NumberFormat 的 getInstance()、getNumberInstance() 等方法都是工厂方法。调用方不需要知道 NumberFormat 内部怎么构造 decimal format。
1 | NumberFormat nf = NumberFormat.getInstance(Locale.US); |
四、策略模式(Strategy)
解决的问题:一组算法可以互换,客户端代码可以根据上下文选择算法,无需修改自身。
1 | public interface PaymentStrategy { |
调用方:
1 | OrderContext order = new OrderContext(); |
切换支付方式只需要 setStrategy(...),不需要改 OrderContext 的代码。
JDK 里的策略模式
java.util.Comparator 就是策略模式的具体应用。不同的排序规则传入不同的 Comparator 实现,集合.sort 方法不需要改动。
1 | List<String> names = Arrays.asList("Tom", "Alice", "Bob"); |
五、观察者模式(Observer)
解决的问题:对象状态变化时,自动通知依赖它的其他对象。
JDK 内置支持:java.util.Observable(已废弃)和 java.util.Observer(已废弃),但思想值得掌握。
手动实现
1 | public interface Observer { |
JDK 里的观察者
Spring 的 ApplicationEvent + ApplicationListener 就是观察者模式。Bean 监听 ApplicationEvent,任何地方发布事件时所有监听器都会收到通知。
1 |
|
六、装饰器模式(Decorator)
解决的问题:动态给对象添加新功能,比继承更灵活,不会导致类膨胀。
类结构
1 | InputStream |
JDK 的 I/O 类库是装饰器模式的经典案例:
1 | // 基础流 |
每个装饰器都包装了上一个流,层层叠加。运行时动态组合功能,比写一个继承链清晰得多。
装饰器 vs 继承
继承在编译时确定功能,装饰器在运行时动态组合。如果用继承:
1 | FileInputStream → BufferedFileInputStream → LineNumberBufferedFileInputStream |
组合一多,类就爆炸了。装饰器模式解决了这个问题。
七、迭代器模式(Iterator)
解决的问题:统一遍历接口,客户端代码不需要知道集合内部结构。
JDK 集合框架全部实现了 java.util.Iterator:
1 | List<String> list = new ArrayList<>(); |
调用方不需要知道 ArrayList 底层是数组还是链表,遍历方式统一。
for-each 的背后
1 | for (String item : list) { |
语法糖,编译器把它变成:
1 | Iterator<String> it = list.iterator(); |
八、模板方法模式(Template Method)
解决问题:算法骨架固定,部分步骤由子类实现,复用共同逻辑。
1 | public abstract class DataProcessor { |
connect() 和 disconnect() 是固定逻辑,子类不需要关心。processData() 由子类实现,提供各自的数据处理逻辑。
JDK 里的模板方法
java.io.InputStream 的 read() 方法就是模板方法:
1 | public int read(byte b[], int off, int len) throws IOException { |
read(byte[], int, int) 是骨架,调用无参数的 read() 由子类实现。
九、总结
| 模式 | 解决问题 | JDK 里的例子 |
|---|---|---|
| 单例 | 全局唯一实例 | java.lang.Runtime |
| 工厂 | 创建和使用分离 | NumberFormat.getInstance() |
| 策略 | 算法可切换 | Comparator |
| 观察者 | 状态变化自动通知 | Spring ApplicationListener |
| 装饰器 | 动态添加功能 | BufferedInputStream |
| 迭代器 | 统一遍历接口 | Iterator |
| 模板方法 | 算法骨架复用 | InputStream.read() |
设计模式不是银弹。它的价值在于让代码结构变得可预测 —— 当你认出某个模式时,你就能预判代码接下来会怎么走。这对读源码、做 Code Review、还有和团队沟通,都很有帮助。