Java 标准库/Android/Spring 库 中的设计模式应用:每个模式只记录一个典型案例
Java 标准库/Android/Spring 库之所以能成为无数应用的基石,除了功能完备之外,一个常被忽视却至关重要的原因在于:它的设计本身就是设计模式最经典的“活教材”。GoF 的 23 种模式并非纸上谈兵,在 java.lang、java.util、java.io 这些开发者每天打交道的包中,几乎都能找到它们的影子。下面从创建型、结构型、行为型三个维度,盘点那些藏在标准库(及主流平台库)源码中的设计模式,并在每个模式后附上调用方视角的代码——因为模式的价值,最终体现在调用方能够以多小的代价获得多大的灵活性。
创建型模式:控制对象的诞生方式
单例模式:java.lang.Runtime
每个 Java 应用都运行在一个 JVM 进程之上,Runtime 类正是这个进程的唯一句柄。它的实现方式是经典的饿汉式单例:
public class Runtime {
private static final Runtime currentRuntime = new Runtime();
private Runtime() {}
public static Runtime getRuntime() {
return currentRuntime;
}
}
私有构造器杜绝了外部实例化,getRuntime() 始终返回同一个 currentRuntime 引用。
调用方代码:
public class MemoryReporter {
public static void main(String[] args) {
Runtime rt1 = Runtime.getRuntime();
Runtime rt2 = Runtime.getRuntime();
System.out.println(rt1 == rt2); // true
long free = rt1.freeMemory();
long total = rt1.totalMemory();
System.out.println("used = " + (total - free) / 1024 / 1024 + " MB");
rt1.addShutdownHook(new Thread(() -> System.out.println("bye")));
}
}
调用方无法 new Runtime(),只能通过 getRuntime() 获取唯一实例。模式在调用方这里的体现就是:获取实例的入口唯一,且拿到的引用始终相同。
工厂模式:List.of() / Set.of() / Map.of()
标准库里静态工厂方法随处可见,而 List.of、Set.of、Map.of 是调用方每天都在用、却很少意识到“这是工厂”的一组设施。它们把“创建集合”从构造器调用变成了对接口的工厂调用:调用方只声明自己要一个 List、Set 或 Map,至于背后是哪个实现类、按元素个数选用哪种内部结构,全部由工厂方法决定。
// 调用方只面向接口,不出现 new ArrayList<>() / new HashSet<>() / new HashMap<>()
List<String> names = List.of("alice", "bob", "carol");
Set<Integer> ids = Set.of(1, 2, 3);
Map<String, Integer> scores = Map.of("alice", 90, "bob", 85);
// 变量类型可以是更抽象的父接口,具体实现对调用方完全透明
Collection<String> c = names;
System.out.println(c.contains("bob")); // true
System.out.println(scores.get("alice")); // 90
// 工厂方法按参数个数返回不同的内部实现
List<String> empty = List.of(); // 共享的空实例
List<String> one = List.of("x"); // 针对 1~2 个元素的紧凑实现
List<String> many = List.of("x", "y", "z");// 元素更多时的通用实现
// 不可变性也由工厂保证,调用方无需自己包一层
try {
names.add("dave"); // 抛 UnsupportedOperationException
} catch (UnsupportedOperationException e) {
System.out.println("immutable");
}
如果打印 List.of()、List.of("x")、List.of("x","y","z") 的 getClass(),会看到不同的内部类名(如 ImmutableCollections$ListN、List12),但这些类并不在公开 API 中,属于实现细节——调用方拿到的只是 List 接口,具体类型由工厂内部选择。创建入口是 of(...) 这样的具名静态方法,返回类型是抽象接口,实现类的选择、实例的复用、不可变性的施加,全部封装在工厂方法内部。
抽象工厂模式:JAXP DocumentBuilderFactory
工厂方法关注“创建一个产品”,抽象工厂关注“创建一族相关产品”。JAXP 中的 DocumentBuilderFactory 正是后者:它本身是抽象类,newInstance() 根据系统属性或 SPI 返回具体工厂,再由这个工厂创建 DocumentBuilder、Document 等一整套 XML 处理对象。
// 调用方通过抽象工厂的静态入口获取具体工厂
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
// 由工厂创建一族产品:DocumentBuilder 及其产出的 Document
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("pom.xml"));
// 同一族中的另一个产品:TransformerFactory → Transformer
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(new DOMSource(doc), new StreamResult(System.out));
调用方代码中不出现任何具体实现类(如 com.sun.org.apache.xerces...),更换 XML 处理器只需调整系统属性或 classpath 中的 SPI 配置。一族产品的创建被收进同一个工厂,调用方只面向抽象工厂编程。
原型模式:Cloneable / Object.clone()
原型模式通过复制已有实例来创建新对象,而不是调用构造器。Java 用 Cloneable 标记接口和 Object.clone() 提供了语言级支持,ArrayList、HashMap 等集合类都实现了它。
ArrayList<String> original = new ArrayList<>(List.of("a", "b"));
@SuppressWarnings("unchecked")
ArrayList<String> copy = (ArrayList<String>) original.clone();
copy.add("c");
System.out.println(original); // [a, b]
System.out.println(copy); // [a, b, c]
// 数组同样支持 clone
int[] src = {1, 2, 3};
int[] dst = src.clone();
dst[0] = 99;
System.out.println(Arrays.toString(src)); // [1, 2, 3]
调用方用 clone() 得到新对象,无需知道 ArrayList 内部数组的扩容策略或 HashMap 的桶结构。复制的细节由被复制对象自己承担,调用方只声明“要一个副本”。需要注意 clone() 默认是浅拷贝,深层复制仍需子类覆写。
建造者模式:StringBuilder
StringBuilder 是一个流式建造者。append() 返回 this,使得多次链式调用可以逐步“建造”出最终的字符串,而建造过程本身与最终产品的表示分离。
String sql = new StringBuilder()
.append("SELECT id, name FROM user")
.append(" WHERE age > ").append(18)
.append(" ORDER BY ").append("id")
.toString();
// 条件化建造:每一步都可选、可跳过
StringBuilder sb = new StringBuilder();
sb.append("Hello");
if (Boolean.TRUE) sb.append(", World");
sb.append('!');
String msg = sb.toString();
调用方通过链式 append 逐步组装,最后 toString() 才产出产品。建造过程与最终对象分离,调用方掌控建造的顺序和内容。
结构型模式:组装与增强的艺术
装饰器模式:Java IO
java.io 包是装饰器模式最 unmistakable 的实现。FileInputStream 是被装饰的组件,BufferedInputStream 是具体装饰器,两者都继承自 InputStream,装饰器在持有被装饰对象的同时为其添加缓冲功能。
// 逐层包裹:字节文件 → 缓冲 → 解压 → 字符解码
try (Reader reader = new BufferedReader(
new InputStreamReader(
new GZIPInputStream(
new FileInputStream("data.gz")),
StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
// 同一套代码换个装饰组合,能力就变了
InputStream in = new BufferedInputStream(new FileInputStream("a.bin"));
调用方通过嵌套构造叠加功能,而变量类型始终是顶层抽象 Reader / InputStream。每包一层就多一项能力,调用方的读取代码一行不改。FilterInputStream 作为抽象装饰器角色,将所有方法默认委托给被包装的流,子类只需覆写需要增强的方法。
适配器模式:Arrays.asList() / InputStreamReader
Arrays.asList() 把数组适配成 List 接口;InputStreamReader 把字节流的 InputStream 适配成字符流的 Reader。
// 数组 → List 接口
String[] arr = {"a", "b", "c"};
List<String> list = Arrays.asList(arr);
System.out.println(list.contains("b")); // true
// 字节流 → 字符流接口
try (Reader r = new InputStreamReader(
new FileInputStream("notes.txt"), StandardCharsets.UTF_8)) {
char[] buf = new char[256];
int n = r.read(buf);
}
调用方想要的是 List 或 Reader 的能力,手上却只有数组或字节流。适配器让原本接口不匹配的两端在调用方这里无缝衔接。
桥接模式:JDBC
JDBC API 是桥接模式在平台级设计中的经典体现。java.sql.Driver、Connection、Statement、ResultSet 这些接口构成了抽象部分,各数据库厂商的驱动实现构成了实现部分。
Class.forName("org.postgresql.Driver");
try (Connection conn = DriverManager.getConnection(
"jdbc:postgresql://localhost:5432/demo", "user", "pwd");
PreparedStatement ps = conn.prepareStatement(
"SELECT id, name FROM t WHERE id = ?")) {
ps.setInt(1, 42);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getInt("id") + " " + rs.getString("name"));
}
}
}
换成 MySQL 只需改驱动类名和 URL,调用方代码一个字都不用动。抽象的 Connection/Statement 与具体数据库实现各自独立演化。
组合模式:Android View / ViewGroup
Android 的视图体系是组合模式在移动端的标准实现:ViewGroup 继承自 View,因此它既是一个可被绘制的“叶子”式组件,又是一个可以容纳其他 View 的“树枝”式容器。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout root = new LinearLayout(this);
root.setOrientation(LinearLayout.VERTICAL);
TextView title = new TextView(this);
title.setText("Settings");
LinearLayout row = new LinearLayout(this); // ViewGroup 也是 View
row.setOrientation(LinearLayout.HORIZONTAL);
row.addView(new Button(this));
row.addView(new Switch(this));
root.addView(title); // 叶子
root.addView(row); // 树枝
setContentView(root);
// 统一操作:无需区分叶子与容器
root.setVisibility(View.VISIBLE);
root.setAlpha(0.95f);
root.setOnClickListener(v -> Log.d("demo", "root clicked"));
}
}
addView(View child) 的参数类型是 View,而 LinearLayout、FrameLayout、RecyclerView 等容器本身也是 View。调用方对 root 调用 setVisibility、setAlpha、setOnClickListener 时,既作用于单个组件,也能递归作用于整棵子树。叶子与树枝共享同一接口,调用方不需要为“单个组件”和“组件集合”写两套逻辑。
外观模式:Spring JdbcTemplate
JdbcTemplate 是 Spring 对 JDBC 子系统的门面。获取连接、创建 Statement、执行 SQL、遍历 ResultSet、处理 SQLException、释放资源这一整套流程,在原生 JDBC 中需要调用方逐步完成;JdbcTemplate 把这些步骤收进一个高层接口。
@Repository
public class UserDao {
private final JdbcTemplate jdbc;
public UserDao(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
// 调用方不出现 Connection / PreparedStatement / ResultSet / try-finally
public List<String> findNamesOlderThan(int age) {
String sql = "SELECT name FROM user WHERE age > ?";
return jdbc.queryForList(sql, String.class, age);
}
public int updateEmail(int id, String email) {
String sql = "UPDATE user SET email = ? WHERE id = ?";
return jdbc.update(sql, email, id);
}
public User findById(int id) {
String sql = "SELECT id, name, email FROM user WHERE id = ?";
return jdbc.queryForObject(sql, (rs, rowNum) ->
new User(rs.getInt("id"), rs.getString("name"), rs.getString("email")),
id);
}
}
对比原生 JDBC:调用方需要 getConnection()、prepareStatement()、setInt()、rs.next()、while 循环、try-catch-finally 关闭资源,还要处理受检的 SQLException。JdbcTemplate 把所有这些“脏活”挡在门面之后,调用方代码里只出现 SQL 和参数。子系统越复杂,外观模式在调用方一侧的收益越明显。
享元模式:Android Message.obtain()
Android 的 Message 类在文档中明确建议:虽然构造器是公开的,但获取 Message 实例的最佳方式是从对象池中“obtain”一个,而不是 new。Message.obtain() 内部维护了一个消息池,已经用过的 Message 在回收后可以再次取出、重置字段后复用。
public class PlaybackService extends Service {
private static final int PLAY = 1;
private static final int STOP = 2;
private final Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case PLAY: startPlayback(); break;
case STOP: stopPlayback(); break;
}
}
};
void requestPlay() {
Message msg = Message.obtain(); // 来自全局消息池
msg.what = PLAY;
msg.obj = "track-001";
handler.sendMessage(msg);
}
void requestStop() {
Message msg = Message.obtain(handler, STOP);
handler.sendMessage(msg);
}
}
调用方写的是 Message.obtain(),而不是 new Message()。对象池、回收、重置这些享元逻辑全部由 Message 内部完成,调用方只负责“设置本次需要变化的字段”然后发送。池中的对象可以被反复借出和归还,但调用方看不到池,只看到一个轻量的 obtain() 入口。
代理模式:java.lang.reflect.Proxy
JDK 动态代理是代理模式在语言层面的支持。Proxy.newProxyInstance() 在运行时生成一个实现指定接口的代理对象,所有方法调用都被转发到 InvocationHandler.invoke()。
interface UserService {
String find(int id);
void save(String name);
}
UserService target = new UserService() {
public String find(int id) { return "user-" + id; }
public void save(String name) { System.out.println("saved " + name); }
};
InvocationHandler handler = (proxy, method, args) -> {
long t0 = System.nanoTime();
try {
return method.invoke(target, args); // 转发给真实对象
} finally {
System.out.printf("%s took %d ns%n",
method.getName(), System.nanoTime() - t0);
}
};
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class<?>[]{UserService.class},
handler);
proxy.find(7); // 调用方写的是接口方法,实际经过代理增强
proxy.save("alice");
调用方拿到的变量类型是接口,但运行期是生成的代理类。增强逻辑由 handler 统一插入,调用方代码保持原样。这一机制后来成为 Spring AOP 的底层基础之一。
行为型模式:算法与职责的分配
策略模式:Comparator
Comparator 是标准库中最清晰的策略接口。Arrays.sort(T[], Comparator) 将排序算法的可变部分——比较规则——抽取为一个独立对象。
String[] names = {"bob", "Alice", "carol", "Dave"};
// 同一份排序骨架,传入不同策略
Arrays.sort(names); // 自然序
Arrays.sort(names, Comparator.reverseOrder()); // 逆序
Arrays.sort(names, String.CASE_INSENSITIVE_ORDER); // 忽略大小写
Arrays.sort(names, Comparator.comparing(String::length)); // 按长度
// 策略对象还可交给有序映射长期持有
Map<String, Integer> map = new TreeMap<>(Comparator.reverseOrder());
map.put("a", 1);
map.put("b", 2);
System.out.println(map.keySet()); // [b, a]
调用方每次只是替换传入的比较器对象,排序算法本身固定不变。比较规则与排序过程在调用点完成解耦。
模板方法模式:AbstractList
AbstractList 定义了集合操作的算法骨架。以 addAll(int index, Collection c) 为例,它规定了“检查索引 → 遍历集合 → 逐个调用 add(index, e)”的固定流程,但 add(int, E) 本身留为抽象方法,由子类提供具体实现。
// 自定义容器:只实现 get / size / add(int,E),其余骨架由 AbstractList 提供
public class FixedList<E> extends AbstractList<E> {
private final Object[] data;
private int size = 0;
public FixedList(int capacity) { data = new Object[capacity]; }
@Override public E get(int index) { return (E) data[index]; }
@Override public int size() { return size; }
@Override public void add(int index, E e) { data[index] = e; size++; }
}
// 调用方:addAll 的"检查索引→逐个插入"骨架来自 AbstractList
List<String> list = new FixedList<>(8);
list.addAll(0, List.of("a", "b", "c"));
System.out.println(list); // [a, b, c]
调用方享用的是 AbstractList 已固化的算法流程,子类只填空缺步骤。调用点看不到流程,但流程被模板牢牢约束。
解释器模式:java.util.regex.Pattern / Matcher
解释器模式定义一种语言,用语法树表示其中的句子,并提供一个解释器来执行这些句子。标准库中最典型的实现就是正则表达式:Pattern.compile() 将正则表达式解析为内部 Pattern.Node 树(如 GroupCurly、CharProperty、Branch 等节点),Matcher 遍历这棵树对输入进行解释匹配。
// 调用方用 compile 构建"解释器",用 matcher 执行"解释"
Pattern pattern = Pattern.compile("(\\d{3})-(\\d{4})");
Matcher matcher = pattern.matcher("Call 555-1234 or 666-7890 now");
while (matcher.find()) {
System.out.println("area=" + matcher.group(1)
+ ", number=" + matcher.group(2));
}
// 同一棵语法树可以反复用于不同输入
Matcher m2 = pattern.matcher("Fax: 111-2222");
System.out.println(m2.matches()); // false
System.out.println(m2.find()); // true
调用方看不到 Pattern.Node 这棵语法树,只面对 compile / matcher / find / group 这几个概念。语法规则与解释执行被封装在 Pattern/Matcher 内部,调用方只负责提供表达式和输入。java.text.DateFormat/SimpleDateFormat 对日期格式串的解释,javax.script 引擎对脚本语言的解释,也都属于同一模式在不同层次上的体现。
责任链模式:java.util.logging / Servlet FilterChain
责任链让多个处理者依次有机会处理请求,处理者之间通过“下一个”引用串联。java.util.logging.Logger 在记录日志时,会先交给自己的 Handler,再沿父 Logger 的层次向上传递;Servlet 的 FilterChain 则把请求依次交给每个 Filter。
// java.util.logging:Handler 链 + 父 Logger 链
Logger logger = Logger.getLogger("com.example");
logger.addHandler(new ConsoleHandler());
logger.addHandler(new FileHandler("app.log"));
logger.info("hello"); // 消息沿 Handler 链和父 Logger 传递
// Servlet FilterChain:每个 Filter 决定是否继续传递
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
if (!authenticated(req)) {
((HttpServletResponse) res).sendError(401);
return; // 中断链
}
chain.doFilter(req, res); // 交给下一个 Filter
}
}
调用方只需注册处理者、发起一次请求,请求沿链传递的过程由链本身驱动,调用方不关心链上有几个节点、谁先谁后。
备忘录模式:java.sql.Savepoint
备忘录模式在不破坏封装的前提下保存对象内部状态,以便之后恢复。JDBC 的 Savepoint 是标准库中最直接的实现:setSavepoint() 记录事务当前状态,rollback(Savepoint) 恢复到该状态。
try (Connection conn = dataSource.getConnection()) {
conn.setAutoCommit(false);
Savepoint before = conn.setSavepoint(); // 保存当前状态(备忘录)
try {
conn.createStatement().executeUpdate(
"UPDATE account SET balance = balance - 100 WHERE id = 1");
conn.createStatement().executeUpdate(
"UPDATE account SET balance = balance + 100 WHERE id = 2");
conn.commit();
} catch (SQLException e) {
conn.rollback(before); // 恢复到备忘录记录的状态
System.out.println("rolled back to savepoint");
}
}
调用方只持有 Savepoint 这个不透明句柄,状态的保存与恢复由数据库连接内部完成,调用方不需要知道事务日志或回滚段的任何细节。
访问者模式:java.nio.file.FileVisitor
访问者模式把“对一组对象的操作”从对象结构中分离出来。Files.walkFileTree() 遍历文件树,每遇到文件或目录就调用 FileVisitor 的对应方法,遍历逻辑固定,而“遇到节点做什么”由调用方提供的访问者决定。
Path start = Paths.get("/tmp");
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
System.out.println("dir: " + dir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
System.out.println("file: " + file + " (" + attrs.size() + " bytes)");
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
System.out.println("skip: " + file);
return FileVisitResult.SKIP_SUBTREE;
}
});
调用方实现访问者接口,walkFileTree 负责遍历结构并在合适的节点上回调。遍历算法与节点操作被拆开,同一棵树可以配上不同的访问者执行不同任务。javax.lang.model.element.ElementVisitor 对注解处理中的元素树也采用了同样的结构。
观察者模式:Observer / Observable
java.util.Observer 接口只有一个 update() 方法,Observable 维护观察者列表并在状态变化时通知它们。
class Stock extends Observable {
private double price;
void setPrice(double p) {
this.price = p;
setChanged();
notifyObservers(p);
}
}
class Logger implements Observer {
public void update(Observable o, Object arg) {
System.out.println("price changed to " + arg);
}
}
Stock stock = new Stock();
stock.addObserver(new Logger());
stock.setPrice(10.5); // 观察者被自动回调
stock.deleteObservers();
stock.setPrice(11.0); // 不再有输出
调用方无需知道谁会收到通知,只需 addObserver 注册、notifyObservers 触发,发布者与订阅者在注册那一刻建立松耦合连接。虽然 Observable 在 Java 9 中被标记为过时,但它仍然是理解观察者模式最直接的 JDK 范本。
迭代器模式:Iterator
Collection 的 iterator() 返回 Iterator,将“遍历”从“容器”中分离出来。
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("b")) it.remove(); // 通过迭代器安全删除
}
System.out.println(list); // [a, c]
// 客户端代码完全不依赖 ArrayList 的内部表示
for (String s : Set.of("x", "y")) {
System.out.println(s);
}
调用方只依赖 Iterator 的两个方法。遍历协议从容器内部剥离出来,成为可互换的独立对象。
命令模式:Runnable
Runnable 将“一个计算或动作”封装为对象,使得动作可以被传递、排队、组合。
Runnable logTask = () -> System.out.println("write log");
Runnable mailTask = () -> System.out.println("send mail");
ExecutorService pool = Executors.newSingleThreadExecutor();
pool.execute(logTask);
pool.execute(mailTask);
new Thread(logTask).start();
pool.shutdown();
logTask 这个变量就是一个命令对象:请求(做什么)与执行者(谁来执行、何时执行)在调用方这里被彻底分开。
中介者模式:Android Activity 协调 View
GoF 的中介者模式通常这样描述:一组同事对象(Colleague)之间需要通信,但若彼此直接持有引用,耦合会随对象数量呈网状增长;于是引入中介者(Mediator),同事只与中介者交互,由中介者负责转发和协调。
Android 的 Activity 正是这种角色。一个界面上的 EditText、Button、TextView 彼此并不持有对方的引用,它们之间的联动全部经由 Activity 中注册的回调完成:
public class FormActivity extends Activity {
private EditText nameInput;
private Button submitButton;
private TextView statusText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
nameInput = new EditText(this);
submitButton = new Button(this);
statusText = new TextView(this);
// Button 不知道 EditText / TextView 的存在,只把点击事件交给 Activity
submitButton.setOnClickListener(v -> {
String name = nameInput.getText().toString();
if (name.isEmpty()) {
statusText.setText("name required");
} else {
statusText.setText("hello, " + name);
}
});
// EditText 也不知道 Button 的存在,只把文本变化交给 Activity
nameInput.addTextChangedListener(new TextWatcher() {
@Override public void beforeTextChanged(CharSequence s, int a, int b, int c) {}
@Override public void onTextChanged(CharSequence s, int a, int b, int c) {}
@Override public void afterTextChanged(Editable s) {
submitButton.setEnabled(s.length() > 0); // 由 Activity 协调另一个 `View`
}
});
LinearLayout root = new LinearLayout(this);
root.setOrientation(LinearLayout.VERTICAL);
root.addView(nameInput);
root.addView(submitButton);
root.addView(statusText);
setContentView(root);
}
}
在这段代码里,submitButton、nameInput、statusText 三个同事对象之间没有任何直接引用。Button 的点击逻辑里出现了 nameInput 和 statusText,但那是 Activity 的字段访问,不是 Button 持有它们;EditText 的文本监听里出现了 submitButton,同样是在 Activity 的匿名内部类中通过外部类字段访问。所有跨 View 的协调都发生在 Activity 这个中介者内部。
与 GoF 标准结构对照:
- Mediator:
Activity(更准确地说,是Activity中承载协调逻辑的那部分)。 - ConcreteColleague:
Button、EditText、TextView。 - 交互方式:GoF 的典型实现中,
Colleague持有Mediator引用并调用mediator.changed(this);Android 这里View并不持有Activity引用,而是通过setOnClickListener、addTextChangedListener把回调注册给Activity,由Activity在回调中读取/修改其他View。这是回调式中介者而非引用式中介者,但“同事之间不直接通信、协调逻辑集中在中介者”这一本质完全一致。
这种设计带来的收益也和中介者模式的目标吻合:新增一个 View 时,只需在 Activity 里加一个字段和相应回调,不需要修改已有 View 的代码;移除某个 View 时,只要 Activity 中不再引用它,其他 View 不受影响。耦合从“View 之间的网状引用”变成了“View 与 Activity 之间的星形回调”。
JDK 自身也有中介者思想的应用,但都不如 Android Activity 这样典型:ExecutorService 可以看作生产者与消费者之间的中介者,java.util.Timer 协调多个定时任务,javax.swing.ButtonGroup 协调一组按钮的选中状态——它们都把“协调”内建在设施中,没有暴露独立的 Mediator 接口,属于中介者思想的应用而非教科书式实现。
状态模式:JDK 中的“弱存在”
状态模式在标准库中没有严格意义上的 GoF 实现。java.lang.Thread 的 Thread.State 枚举描述了线程的生命周期状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED),状态转换由 JVM 驱动,调用方通过 getState() 观察。但线程的行为并非由状态对象多态分发——Thread 没有把“每个状态下的行为”委托给独立的 State 子类,因此只能算“状态思想”的体现,而非严格的状态模式。真正接近状态模式的代码往往出现在应用层(如订单状态机、工作流引擎),而不是 JDK 内部。
写在最后
标准库对设计模式的使用有一个共同特征:模式服务于库的扩展性与稳定性,而非为了模式而模式。Java IO 用装饰器让流的功能可以任意叠加;集合框架用迭代器和策略让遍历与比较从容器中解耦;AbstractList 用模板方法把公共算法固化在抽象层;Proxy 用动态代理把横切关注点的插入推迟到运行时;JAXP 用抽象工厂统一一族 XML 产品的创建;Pattern/Matcher 用解释器把正则语法与匹配执行封装在一起;FileVisitor 用访问者把遍历算法与节点操作拆开;Android 用 ViewGroup 把“整体与部分”统一为同一接口,用 Message.obtain() 把高频对象的复用藏进池中,用 Activity 把 View 之间的网状通信收敛为星形回调;Spring 用 JdbcTemplate 把 JDBC 的复杂度隔离在门面之后。这些设计使得标准库与平台库在保持核心接口稳定的同时,允许实现者、调用者、扩展者各自在恰当的层次上参与进来。
而调用方代码最能说明设计模式的实际价值:模式的存在感往往不在调用处,而在调用方能够以多小的代价获得多大的灵活性。new BufferedReader(new FileInputStream(...)) 只多一层括号就换来了缓冲能力;Arrays.sort(arr, cmp) 只多一个参数就换掉了排序规则;Pattern.compile(...) 只多一行就把一套匹配语言交给调用方;root.addView(row) 让容器和组件并排出现、被同一套操作覆盖;Message.obtain() 只换了一个获取入口就绕开了高频分配;setOnClickListener 只注册一个回调就让 View 不必持有彼此的引用。读 JDK 源码时带着“这里用了什么模式”的视角,往往能比单纯记忆模式定义获得更具体的判断力:你会看到模式在真实代码中常常是混合的、半透明的、带折中的——Activity 用回调而非同事持有中介者引用,Observable 被标记过时,Thread.State 只借了状态模式的思想——而这种“不纯粹”恰恰是工程设计的常态。

浙公网安备 33010602011771号