我的博客一直有一些积压很久的草稿,有些完成度太低、内容过时的我不定期会删除;另一些完成度比较高,但随着工作变动、时间过去太久的博文,无力继续研究相关方向和更新。好在 AI 发展迅速,我借助 DeepSeek 做了 review,修改掉格式错误和笔误,并补充了缺失的章节,算完成了小小的心愿。
本文最初写于 2020-01-05 02:37,我使用 AI 调整/补充了全文 markdown 格式、代码块缩进,以及文末”其他使用到的设计模式(AI 补充)”章节。
关于设计模式这个话题,很早以前我曾经写过一篇《大话设计模式》Python版代码实现,前些日子还做了一些更新。
关于JDK里的设计模式,也有人总结过JDK里的设计模式,但是没有更进一步的展示,本文具体看看实现是什么样的。本文仅仅挑选了一部分有代表性的类和方法,其他未提到的可以自行看代码。也是对之前所学知识的复习。
需要说明的是:
- JDK里这些代码,作者当时可能只是出于习惯而已,并没有想往设计模式上靠,因此有的解读可能会比较生硬。设计模式不应该生搬硬套,而是因地制宜。
- 设计模式 ≠ 面向对象。设计模式仅仅是设计模式,是一套编码实践的方法论,只不过经常和OOP相关联起来。
将设计模式分为结构模式、生产模式和行为模式是经典的四人帮设计模式(以下简称GOF)的分类方式,本文也沿用了这种分类。这几类具体的作用:
- 结构模式:处理类或对象的组合
- 创建模式:创建对象的方式
- 行为模式:算法和对象间的职责分配
结构模式 Structural
适配器模式 Adaptor
把一个类的接口换成希望使用的接口。
举例:java.util.Arrays#asList()
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
其中,ArrayList的结构以及这个构造方法为:
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
// 忽略其他代码
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
}
因为ArrayList底层就是一个数组a,因此这里你看不到任何结构上的转换,只有对外接口的转换。
桥接模式 Bridge
将一个类的抽象部分剥离出来,允许更丰富的变化。
如果我写的《大话设计模式》Python版代码实现里手机和手机软件的关系不好理解,可以参考桥接里毛笔与颜色的例子。直观来看,就是一个(抽象)类A中的成员B,仍然是接口或抽象类。A和B可以独立的扩展。
举例:JDBC
JDBC的情况比较复杂,其中Connection接口和PreparedStatement接口即为桥接器模式。由于涉及源码较多,具体可以参考JDBC源码分析&桥接模式。
组合模式 Composite
将对象组合成树形结构以表示”整体-部分”的层次结构,单个对象和组合对象使用方式具有一致性。
举例1:java.util.Map#putAll(Map)、java.util.List#addAll(Collection)、java.util.Set#addAll(Collection)
这个例子包括其他几个例子都不是很恰当,因为Map不是Map的子类;事实上Java是不能循环继承的。仅仅是接口层面看上去符合接口和自身同属一个类。
装饰器模式 Decorator
给一个对象新增额外的功能,提供这些功能的类,和这个对象没有继承关系。
这里有一个问题:作为装饰器,是要感知被装饰的对象的类型或接口的,否则没有办法递归地调用被装饰对象的方法。
举例:java.io.BufferedInputStream
BufferedInputStream继承了FilterInputStream,而FilterInputStream封装了一个InputStream对象。
BufferedInputStream使用了一个数组做缓冲区,从而提供缓冲输入的功能。
FilterInputStream extends InputStream {
/**
* The input stream to be filtered.
*/
protected volatile InputStream in;
...
}
public
class BufferedInputStream extends FilterInputStream {
protected volatile byte buf[];
...
}
外观模式 Facade
为一组调用提供一个统一的门面。调用这个门面时会执行这些调用。
举例:java.lang.Class
实例太多了,随便选一个吧。这个方法调用的另外两个方法也是Class里的方法。
public boolean isLocalClass() {
return isLocalOrAnonymousClass() && !isAnonymousClass();
}
享元模式 Flyweight
有效率的存储大量细粒度的对象。
注意,这个解释是结果,并不是手段。简单来说,为了共”享”不变的”元”数据,很容易想到使用持久化的缓存。
举例:基本数据类型中,对于特定范围的值,是会缓存为常量值的,如
java.lang.Integer#valueOf(int)
java.lang.Boolean#valueOf(boolean)
java.lang.Byte#valueOf(byte)
java.lang.Character#valueOf(char)
这里有个引申话题:Integer的1,用”==”判等的结果是?两个129呢?可以编写一下代码做个测试。
代理模式 Proxy
为其他对象提供一种代理以控制对这个对象的访问。
注意,这里强调”控制”,是和其他结构模式的最大的区别。
举例:java.lang.reflect.Proxy
如何使用Proxy类来代理一个对象的方法本文不再复述。
创建模式 Creational
抽象工厂 Abstract Factory
创建一组有关联或相互依赖对象的接口,而无需指定它们的实现。
举例:java.util.ResourceBundle#getBundle()
java.util.Calendar#getInstance()、java.util.Arrays#asList()我个人感觉不太像。java.util.ResourceBundle#getBundle()有点沾边,可以扫一眼:
public abstract class ResourceBundle {
public static ResourceBundle getBundle(String baseName, Locale targetLocale,
ClassLoader loader, Control control) {
if (loader == null || control == null) {
throw new NullPointerException();
}
return getBundleImpl(baseName, targetLocale, loader, control);
}
private static ResourceBundle getBundleImpl(String baseName, Locale locale,
ClassLoader loader, Control control) {
if (locale == null || control == null) {
throw new NullPointerException();
}
// We create a CacheKey here for use by this call. The base
// name and loader will never change during the bundle loading
// process. We have to make sure that the locale is set before
// using it as a cache key.
CacheKey cacheKey = new CacheKey(baseName, locale, loader);
ResourceBundle bundle = null;
// Quick lookup of the cache.
BundleReference bundleRef = cacheList.get(cacheKey);
if (bundleRef != null) {
bundle = bundleRef.get();
bundleRef = null;
}
//后续忽略
...
}
//后续忽略
...
}
ResourceBundle是抽象类。调用getBundle时,调用了BundleReference的方法,而这个方法本身是在抽象类Reference中的,大体是满足这个定义的。
建造者模式 Builder
将一个复杂对象的构建(Director)与它的表示(Builder)分离,使得同样的构建过程可以创建不同的表示(ConcreteBuilder)。
举例:java.lang.StringBuilder#append()
分离的是String对象和String对象的构建过程,这样解释可能有点牵强但是也能满足。
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
工厂方法模式 Factory Method
定义一个用于创建对象的接口,让子类决定实例化哪一个类。
举例:java.lang.Object#toString()
很常见的用法是,重写(override)子类toString方法。虽然子类只能实例化一个类,但是可以有很多子类。
原型模式 Prototype
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
举例:java.lang.Object#clone()、java.lang.Cloneable
单例模式 Singleton
只允许一个类只有一个实例。
举例:enum
行为模式 Behavioral
职责链模式 Chain of Responsibility
使多个对象都有机会处理请求,从而避免发送者和接收者的耦合关系。将对象连成链并沿着这条链传递请求直到被处理
举例:java.util.logging.Logger#log()
public void log(LogRecord record) {
if (!isLoggable(record.getLevel())) {
return;
}
Filter theFilter = filter;
if (theFilter != null && !theFilter.isLoggable(record)) {
return;
}
// Post the LogRecord to all our Handlers, and then to
// our parents' handlers, all the way up the tree.
Logger logger = this;
while (logger != null) {
final Handler[] loggerHandlers = isSystemLogger
? logger.accessCheckedHandlers()
: logger.getHandlers();
for (Handler handler : loggerHandlers) {
handler.publish(record);
}
final boolean useParentHdls = isSystemLogger
? logger.useParentHandlers
: logger.getUseParentHandlers();
if (!useParentHdls) {
break;
}
logger = isSystemLogger ? logger.parent : logger.getParent();
}
}
我对logger不是特别熟,这里可以看到logger向上一级查找并执行。
命令模式 Command
将请求封装为对象
举例:java.lang.Runable/java.util.concurrent.Callable
实现的run方法,就是一系列的命令。将这些命令封装成了一个实现Runable接口的对象。
解释器模式 Interpreter
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
举例:java.util.regex.Pattern
正则表达式。解释=解析+后续动作。
迭代器模式 Iterator
按一定顺序方法访问一个聚合对象的各个元素,又不暴露这个对象的内部表示。
举例:java.util.Iterator
容器用的太多就不贴代码了。
注意:java.util.Iterator是java.util.IEnumeration的替代品。
中介者模式 Mediator
用一个对象封装一系列对象的交互,使得这些被封装的对象不需要相互引用。
举例:
如果使用java.util.concurrent.Executor#execute()和java.util.concurrent.ExecutorService#submit()来举例,那么只有当提交的任务之间有交互却又没有相互引用时,才符合这个定义。
比如生产者-消费者问题中,厨师和使者只感知上菜窗口是否有菜,而不相互感知,此时符合这个定义。
备忘录模式 Memento
在不破坏封装性的前提下捕获一个对象的内部状态,并在该对象之外保存这个状态,以后可以将对象恢复到这个状态。
举例:java.util.Date的fastTime
但是这个属性是对象内部的,只有将这个值在对象外保存时,才能满足定义。比如,将一个Date对象持久化到数据库,读取时再恢复回来,那么我们可以保存这个long值,此时整个过程才满足这个定义。
空对象模式 Null Object
GOF没有提到。简单举几个例子:
- java.util.Collections#emptyList()
- java.util.Optional.ofNullable()
观察者模式 Observer
定义了一种一对多的关系,让多个观察对象同时监听一个主题对象,当主题对象状态发生变化时会通知所有观察者。
举例:
我很奇怪为什么这里没有拿Object.notifyAll()来举例。java.util.EventListener是个没有任何方法的接口,它的实现很多而且都不是很常用。
状态模式 State
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
举例:java.util.Iterator
迭代时,是否有后继会影响其行为。但是这个和状态改变似乎关系不大。
策略模式 Strategy
定义算法家族并且分别封装,它们之间可以相互替换而不影响客户端。
举例:java.util.Comparator#compare()
Comparator是算法家族,传入Comparator的地方会根据不同算法出现不同行为,比如对一组员工的List,按年龄排序、按身高排序等。
模板方法模式 Template Method
定义一个操作中的算法骨架,将一些步骤延迟至子类中。
举例:java.util.Collections#sort()
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
访问者模式 Visitor
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
举例:java.nio.file.FileVisitor
这个接口不是很确定,它确实有遍历访问路径下文件的行为,但是额外新操作倒是没有。
其他使用到的设计模式(AI 补充)
以下内容由 AI 生成,仅列出模式名称与 JDK 中的典型示例,不展开分析。有兴趣的读者可自行查阅源码。
| 分类 | 设计模式 | JDK 示例 | 简要说明 |
|---|---|---|---|
| 创建 | 对象池模式 Object Pool | java.util.concurrent.ThreadPoolExecutor / Executors |
复用线程对象,避免频繁创建销毁 |
| 创建 | 依赖注入 Dependency Injection | java.util.ServiceLoader |
运行时发现并加载接口实现,解耦接口与实现 |
| 结构 | 标记接口 Marker Interface | java.io.Serializable |
空接口为类附加元数据语义(如可序列化) |
| 行为 | 不可变模式 Immutable | java.lang.String / java.math.BigDecimal |
对象创建后状态不可变,天然线程安全 |
| 行为 | 流式接口 Fluent Interface | java.util.stream.Stream |
链式调用,每个方法返回自身类型 |
| 行为 | 读写锁 Read-Write Lock | java.util.concurrent.locks.ReentrantReadWriteLock |
读共享、写互斥,适合读多写少 |
| 行为 | 生产者-消费者 Producer-Consumer | java.util.concurrent.BlockingQueue |
阻塞队列解耦生产者与消费者线程 |
| 并发 | Reactor 模式 | java.nio.channels.Selector |
单线程事件循环处理多路 I/O 就绪事件 |
| 并发 | 双重检查锁定 Double-Checked Locking | java.util.concurrent.ConcurrentHashMap 内部实现 |
减少同步开销的延迟初始化手段 |
| 并发 | Balking 模式 | java.util.concurrent.FutureTask |
操作只执行一次,后续调用直接返回 |
文章摘自:https://www.cnblogs.com/wuyuegb2312/p/20541933
