我们来深入、详尽地探讨Java的三大核心设计模式:责任链模式、工厂模式(涵盖其多种形态)和观察者模式。本文将超过5000字,包含详细的原理剖析、丰富的代码示例、实际应用场景以及深度精讲。
第一部分:责任链模式 (Chain of Responsibility Pattern)
一、 原理与核心思想
责任链模式是一种行为设计模式,其核心思想是将请求的发送者和接收者解耦。它允许你将多个处理对象(称为处理器或处理节点)连接成一条链。当一个请求被发出后,它会沿着这条链传递,直到链上的某个对象决定处理它为止。
关键角色:
- 抽象处理器 (Handler): 定义一个处理请求的接口(或抽象类)。通常包含一个指向下一个处理器的引用(nextHandler)和一个处理请求的方法(如 handleRequest)。
- 具体处理器 (Concrete Handler): 实现了抽象处理器的接口,负责处理它所能负责的请求。如果自己不能处理,则将请求转发给下一个处理器。
- 客户端 (Client): 创建处理链,并将请求发送到链上的第一个处理器。
UML 示意图:
text
[Client] --> [Handler]
| - nextHandler: Handler
| + handleRequest()
| + setNext()
/ \
/ \
[ConcreteHandlerA] [ConcreteHandlerB]
+ handleRequest() + handleRequest()
二、 使用场景
- 多级审批流程: 如请假审批,需要经过项目经理、部门经理、总经理等层级,每个层级有各自的审批权限。
- 异常处理: Java中的 try-catch 块就是一个责任链。异常会从最内层的 catch 开始匹配,直到找到能处理该异常的类型为止。
- 过滤器/拦截器链: 在Web开发中极为常见,如Servlet中的 FilterChain、Spring MVC中的 HandlerInterceptor。一个HTTP请求需要经过一系列过滤器(字符编码、安全校验、日志记录等)的处理。
- 日志记录器: 不同的日志级别(DEBUG, INFO, WARN, ERROR)可以被不同层级的日志处理器处理(控制台输出、文件记录、发送警报等)。
三、 代码示例:多级审批系统
我们来实现一个员工请假审批的系统。请假天数不同,需要不同级别的领导审批。
1. 抽象处理器:审批者类 (Approver)
java
/**
* 抽象审批者(抽象处理器)
*/
public abstract class Approver {
// 核心:指向下一个处理者的引用
protected Approver nextApprover;
// 设置下一个审批者
public void setNext(Approver nextApprover) {
this.nextApprover = nextApprover;
}
// 处理审批请求的方法
public abstract void processRequest(LeaveRequest request);
}
2. 具体处理器:各级领导 (ProjectManager, DepartmentManager, CEO)
java
/**
* 具体处理器:项目经理
*/
public class ProjectManager extends Approver {
@Override
public void processRequest(LeaveRequest request) {
if (request.getDays() <= 3) {
System.out.println("项目经理批准了 " + request.getEmployeeName() + " 的 " + request.getDays() + " 天假期。");
} else {
// 自己无法处理,传递给下一个
if (nextApprover != null) {
System.out.println("项目经理无权限审批,转交上级...");
nextApprover.processRequest(request);
} else {
System.out.println("请假申请无人可处理!");
}
}
}
}
/**
* 具体处理器:部门经理
*/
public class DepartmentManager extends Approver {
@Override
public void processRequest(LeaveRequest request) {
if (request.getDays() <= 7) {
System.out.println("部门经理批准了 " + request.getEmployeeName() + " 的 " + request.getDays() + " 天假期。");
} else {
if (nextApprover != null) {
System.out.println("部门经理无权限审批,转交上级...");
nextApprover.processRequest(request);
} else {
System.out.println("请假申请无人可处理!");
}
}
}
}
/**
* 具体处理器:CEO
*/
public class CEO extends Approver {
@Override
public void processRequest(LeaveRequest request) {
if (request.getDays() <= 15) {
System.out.println("CEO批准了 " + request.getEmployeeName() + " 的 " + request.getDays() + " 天假期。");
} else {
System.out.println("CEO驳回了 " + request.getEmployeeName() + " 的 " + request.getDays() + " 天假期申请,假期太长!");
// CEO是最终节点,没有nextApprover,链到此结束
}
}
}
3. 请求类 (LeaveRequest)
java
/**
* 请假请求
*/
public class LeaveRequest {
private String employeeName;
private int days;
public LeaveRequest(String employeeName, int days) {
this.employeeName = employeeName;
this.days = days;
}
// Getter methods...
public String getEmployeeName() { return employeeName; }
public int getDays() { return days; }
}
4. 客户端:组装责任链并提交请求
java
/**
* 客户端
*/
public class ChainOfResponsibilityClient {
public static void main(String[] args) {
// 1. 创建处理节点
Approver pm = new ProjectManager();
Approver dm = new DepartmentManager();
Approver ceo = new CEO();
// 2. 组装责任链:项目经理 -> 部门经理 -> CEO
pm.setNext(dm);
dm.setNext(ceo);
// 注意:CEO后面没有设置next,它是链的末端
// 3. 创建不同的请假请求
LeaveRequest request1 = new LeaveRequest("张三", 2);
LeaveRequest request2 = new LeaveRequest("李四", 5);
LeaveRequest request3 = new LeaveRequest("王五", 10);
LeaveRequest request4 = new LeaveRequest("赵六", 20);
System.out.println("===== 处理张三的2天申请 =====");
pm.processRequest(request1); // 应由项目经理处理
System.out.println("\n===== 处理李四的5天申请 =====");
pm.processRequest(request2); // 项目经理 -> 部门经理
System.out.println("\n===== 处理王五的10天申请 =====");
pm.processRequest(request3); // 项目经理 -> 部门经理 -> CEO
System.out.println("\n===== 处理赵六的20天申请 =====");
pm.processRequest(request4); // 项目经理 -> 部门经理 -> CEO (驳回)
}
}
输出结果:
text
===== 处理张三的2天申请 =====
项目经理批准了 张三 的 2 天假期。
===== 处理李四的5天申请 =====
项目经理无权限审批,转交上级...
部门经理批准了 李四 的 5 天假期。
===== 处理王五的10天申请 =====
项目经理无权限审批,转交上级...
部门经理无权限审批,转交上级...
CEO批准了 王五 的 10 天假期。
===== 处理赵六的20天申请 =====
项目经理无权限审批,转交上级...
部门经理无权限审批,转交上级...
CEO驳回了 赵六 的 20 天假期申请,假期太长!
四、 精讲与深入
- 优点:
- 降低耦合度: 客户端无需知道请求由谁处理,处理者也不知道链的结构。双方都只依赖于抽象接口。
- 增强灵活性: 可以动态地增加、修改或删除链上的处理者,符合开闭原则。
- 简化对象: 每个处理者只需关注自己职责范围内的请求,无需关心其他逻辑。
- 缺点:
- 请求不一定被处理: 如果链配置不完整,请求可能到达链的末端也无法被处理。
- 性能影响: 链过长可能会影响性能,特别是在递归调用深度很大时。
- 调试困难: 请求的传递是沿着链进行的,调试时逻辑可能不那么直观。
- 变体:
- 功能链: 每个处理器都对请求做一些处理,然后传递给下一个,而不是选择性地处理。Web Filter就是典型例子。
- 提前终止: 处理器处理完后,可以决定是否继续传递请求。
第二部分:工厂模式 (Factory Pattern)
工厂模式是一个创建型模式的家族,它提供了一种创建对象的最佳方式,而无需将实例化逻辑暴露给客户端。它主要分为三种:简单工厂模式、工厂方法模式、抽象工厂模式。
一、 简单工厂模式 (Simple Factory Pattern)
原理: 定义一个工厂类,它根据传入的参数,动态决定创建哪一种产品类的实例。
代码示例:图形工厂
java
// 1. 产品接口
public interface Shape {
void draw();
}
// 2. 具体产品
public class Circle implements Shape {
@Override
public void draw() { System.out.println("Drawing a Circle"); }
}
public class Rectangle implements Shape {
@Override
public void draw() { System.out.println("Drawing a Rectangle"); }
}
// 3. 简单工厂
public class ShapeFactory {
// 核心工厂方法,根据类型字符串创建对象
public static Shape createShape(String type) {
if (type == null) return null;
if (type.equalsIgnoreCase("CIRCLE")) {
return new Circle();
} else if (type.equalsIgnoreCase("RECTANGLE")) {
return new Rectangle();
}
// 如果需要新增产品,必须修改这里的代码,违反开闭原则
throw new IllegalArgumentException("Unknown shape type: " + type);
}
}
// 4. 客户端
public class Client {
public static void main(String[] args) {
Shape circle = ShapeFactory.createShape("CIRCLE");
circle.draw(); // Output: Drawing a Circle
Shape rectangle = ShapeFactory.createShape("RECTANGLE");
rectangle.draw(); // Output: Drawing a Rectangle
}
}
精讲: 简单工厂违背了“开闭原则”(对扩展开放,对修改关闭)。每次新增产品都需要修改工厂类的逻辑。但它简单易懂,适用于产品类型不多且稳定的情况。
二、 工厂方法模式 (Factory Method Pattern)
原理: 定义一个用于创建对象的接口(工厂接口),但让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
代码示例:日志记录器工厂
假设我们需要一个日志系统,可以支持将日志记录到本地文件或者数据库。
java
// 1. 产品接口
public interface Logger {
void log(String message);
}
// 2. 具体产品
public class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("Logging to FILE: " + message);
}
}
public class DatabaseLogger implements Logger {
@Override
public void log(String message) {
System.out.println("Logging to DATABASE: " + message);
}
}
// 3. 抽象工厂(创建者)
public abstract class LoggerFactory {
// 工厂方法,由子类实现
public abstract Logger createLogger();
// 还可以在抽象工厂中定义一些公共逻辑
public void logMessage(String message) {
Logger logger = this.createLogger(); // 调用工厂方法
logger.log(message);
}
}
// 4. 具体工厂
public class FileLoggerFactory extends LoggerFactory {
@Override
public Logger createLogger() {
// 可能包含复杂的初始化逻辑,如读取配置、创建文件等
return new FileLogger();
}
}
public class DatabaseLoggerFactory extends LoggerFactory {
@Override
public Logger createLogger() {
// 可能包含数据库连接等逻辑
return new DatabaseLogger();
}
}
// 5. 客户端
public class Client {
public static void main(String[] args) {
LoggerFactory factory;
// 根据配置或环境决定使用哪个工厂
String config = "database"; // 可以来自配置文件
if ("database".equals(config)) {
factory = new DatabaseLoggerFactory();
} else {
factory = new FileLoggerFactory();
}
// 使用工厂创建产品并使用
factory.logMessage("This is a log message!");
// Output: Logging to DATABASE: This is a log message!
}
}
精讲:
- 符合开闭原则: 要新增一种日志方式(如网络日志),只需新增一个 NetworkLogger 和一个 NetworkLoggerFactory,无需修改任何现有代码。
- 核心是“继承”: 通过让子类实现工厂方法来决定创建何种对象,将对象创建的责任完全委托给了子类。
- JDK中的应用: java.util.Calendar#getInstance(), java.text.NumberFormat#getInstance() 等方法本质上使用了工厂方法模式。
三、 抽象工厂模式 (Abstract Factory Pattern)
原理: 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。它是工厂的工厂。
代码示例:GUI 皮肤库
假设我们要开发一个跨平台的GUI工具包,需要为不同操作系统(Windows, Mac)创建一套UI控件(按钮、复选框)。
java
// 1. 抽象产品族
public interface Button {
void render();
}
public interface Checkbox {
void render();
}
// 2. 具体产品族 - Windows系列
public class WindowsButton implements Button {
@Override public void render() { System.out.println("Rendering a Windows-style Button"); }
}
public class WindowsCheckbox implements Checkbox {
@Override public void render() { System.out.println("Rendering a Windows-style Checkbox"); }
}
// 3. 具体产品族 - Mac系列
public class MacButton implements Button {
@Override public void render() { System.out.println("Rendering a Mac-style Button"); }
}
public class MacCheckbox implements Checkbox {
@Override public void render() { System.out.println("Rendering a Mac-style Checkbox"); }
}
// 4. 抽象工厂 (核心)
public interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
// 5. 具体工厂 - Windows工厂
public class WindowsFactory implements GUIFactory {
@Override public Button createButton() { return new WindowsButton(); }
@Override public Checkbox createCheckbox() { return new WindowsCheckbox(); }
}
// 6. 具体工厂 - Mac工厂
public class MacFactory implements GUIFactory {
@Override public Button createButton() { return new MacButton(); }
@Override public Checkbox createCheckbox() { return new MacCheckbox(); }
}
// 7. 客户端代码。它只依赖于抽象工厂和抽象产品。
public class Application {
private Button button;
private Checkbox checkbox;
public Application(GUIFactory factory) {
// 客户端不知道它创建的是什么具体产品,只知道它们是Button和Checkbox
this.button = factory.createButton();
this.checkbox = factory.createCheckbox();
}
public void render() {
button.render();
checkbox.render();
}
}
// 8. 程序的配置部分,根据运行时环境选择工厂
public class ApplicationConfigurator {
public static void main(String[] args) {
GUIFactory factory;
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("win")) {
factory = new WindowsFactory();
} else if (osName.contains("mac")) {
factory = new MacFactory();
} else {
throw new RuntimeException("Unknown operating system.");
}
Application app = new Application(factory);
app.render();
/* Output on Windows:
Rendering a Windows-style Button
Rendering a Windows-style Checkbox
*/
}
}
精讲:
- 解决“产品族”创建问题: 它强调创建一整套相关的对象(例如,要么全部是Windows风格,要么全部是Mac风格),保证产品之间的兼容性。
- 与工厂方法的区别:
- 工厂方法: 针对一个产品等级结构(Logger)。
- 抽象工厂: 针对多个产品等级结构(Button, Checkbox...),形成一个产品族。
- 缺点: 难以支持新种类的产品。如果要在 GUIFactory 中新增一个 createTextBox() 方法,那么所有具体工厂及其所有子类都需要修改,这违反了开闭原则。
第三部分:观察者模式 (Observer Pattern)
一、 原理与核心思想
观察者模式是一种行为设计模式,它定义了对象间的一种一对多的依赖关系。当一个对象(称为主题Subject)的状态发生改变时,所有依赖于它的对象(称为观察者Observers)都会得到通知并自动更新。
关键角色:
- 主题 (Subject): 持有观察者的引用集合,提供注册、注销观察者的方法,以及通知观察者的方法。
- 具体主题 (Concrete Subject): 实现主题接口,在状态改变时通知所有注册的观察者。
- 观察者 (Observer): 定义一个更新接口,用于在主题状态改变时接收通知。
- 具体观察者 (Concrete Observer): 实现观察者接口,维护一个对具体主题的引用,并存储与主题相关的状态,这些状态应与主题的状态保持一致。
二、 使用场景
- 事件驱动系统: GUI编程中的按钮点击、鼠标移动等事件监听。
- 发布-订阅模型: 消息队列、新闻推送、微博/微信公众号关注。
- 模型-视图-控制器 (MVC) 架构: 当模型(数据)发生变化时,会自动通知相关的视图进行更新。
- 监控系统: 当被监控的指标(如CPU使用率)达到阈值时,通知警报器。
三、 代码示例:气象站监测系统
这是一个经典例子:一个气象站(主题)负责追踪温度、湿度、气压等数据。多个布告板(观察者)需要实时显示最新的数据。
1. 主题接口 (Subject)
java
public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers(); // 通知所有观察者
}
2. 观察者接口 (Observer)
java
public interface Observer {
// 当主题状态改变时,调用此方法。可以传递参数,也可以用“拉”的模式让观察者自己从主题获取数据。
void update(float temperature, float humidity, float pressure);
}
3. 布告板显示接口 (DisplayElement) - 可选
java
public interface DisplayElement {
void display(); // 显示数据
}
4. 具体主题:WeatherData
java
import java.util.ArrayList;
import java.util.List;
public class WeatherData implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
this.observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
// 遍历所有观察者,并调用其update方法
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
// 当从气象站得到新的测量数据时,调用此方法
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged(); // 数据改变,通知观察者
}
private void measurementsChanged() {
notifyObservers();
}
// 其他Getter方法...
}
5. 具体观察者:当前条件布告板 (CurrentConditionsDisplay)
java
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData; // 保留对Subject的引用,方便以后注销
// 构造函数中注册自己
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
// 当收到通知时,更新数据
this.temperature = temperature;
this.humidity = humidity;
display(); // 然后显示
}
@Override
public void display() {
System.out.printf("Current conditions: %.1f°C and %.1f%% humidity%n", temperature, humidity);
}
}
6. 具体观察者:统计布告板 (StatisticsDisplay) - 另一个观察者
java
public class StatisticsDisplay implements Observer, DisplayElement {
private float maxTemp = 0.0f;
private float minTemp = 200;
private float tempSum = 0.0f;
private int numReadings;
private Subject weatherData;
public StatisticsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
tempSum += temperature;
numReadings++;
if (temperature > maxTemp) maxTemp = temperature;
if (temperature < minTemp) minTemp = temperature;
display();
}
@Override
public void display() {
System.out.printf("Avg/Max/Min temperature = %.1f/%.1f/%.1f%n",
(tempSum / numReadings), maxTemp, minTemp);
}
}
7. 测试程序(客户端)
java
public class WeatherStation {
public static void main(String[] args) {
// 创建主题(被观察者)
WeatherData weatherData = new WeatherData();
// 创建观察者,并在构造时注册到主题
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
// 模拟新的气象测量数据到达
System.out.println("--- First Measurement ---");
weatherData.setMeasurements(26, 65, 1013);
System.out.println();
System.out.println("--- Second Measurement ---");
weatherData.setMeasurements(27, 70, 1012);
System.out.println();
System.out.println("--- Third Measurement ---");
weatherData.setMeasurements(25, 90, 1014);
// 可以动态移除观察者
// weatherData.removeObserver(statisticsDisplay);
}
}
输出结果:
text
--- First Measurement ---
Current conditions: 26.0°C and 65.0% humidity
Avg/Max/Min temperature = 26.0/26.0/26.0
--- Second Measurement ---
Current conditions: 27.0°C and 70.0% humidity
Avg/Max/Min temperature = 26.5/27.0/26.0
--- Third Measurement ---
Current conditions: 25.0°C and 90.0% humidity
Avg/Max/Min temperature = 26.0/27.0/25.0
四、 精讲与深入 (含Java内置支持)
- 推 (Push) vs 拉 (Pull) 模型:
- 推模型(如上例): 主题将数据直接通过 update() 方法的参数推送给观察者。简单直接,但可能推送观察者不需要的数据。
- 拉模型: 主题只通知观察者“我变了”,观察者收到通知后,主动调用主题的Getter方法来“拉取”它需要的数据。更灵活,但观察者需要持有主题的引用。Java内置的观察者模式使用拉模型。
- Java内置的观察者模式 (java.util.Observer & java.util.Observable):
- Observable 类相当于 Subject。
- Observer 接口相当于我们的 Observer。
- 不推荐使用! 因为 Observable 是一个类而不是接口,这限制了它的使用(Java不支持多重继承)。而且它的 setChanged() 方法是 protected 的,意味着你不能在不继承 Observable 的情况下组合使用它。自Java 9起,它已被标记为 @Deprecated(since="9")。
- 优点:
- 解耦: 主题和观察者之间是抽象耦合,主题不知道观察者的具体细节。
- 支持广播通信: 一个主题的改变可以通知任意数量的观察者。
- 缺点:
- 意外的更新: 观察者不知道其他观察者的存在,一个观察者的操作可能触发一系列连锁反应,导致系统难以跟踪和调试。
- 性能考虑: 如果观察者很多,或者更新操作非常耗时,通知所有观察者可能会成为性能瓶颈。
- 现代实现: 在现代Java开发中,更推荐使用事件监听机制或反应式编程库(如Project Reactor, RxJava)来实现观察者模式,它们提供了更强大、更灵活、异步非阻塞的支持。
总结与对比
模式 | 类型 | 核心目的 | 关键思想 |
责任链模式 | 行为型 | 解耦请求发送与接收,让多个对象都有机会处理请求 | 构建一条处理链,请求沿链传递,直至被处理 |
工厂模式 | 创建型 | 解耦对象创建与使用 | 将 new 操作封装起来,由工厂负责实例化 |
观察者模式 | 行为型 | 定义一对多的依赖,实现状态变化的通知 | 主题状态改变,自动通知所有依赖它的观察者 |
这三大模式从不同维度解决了软件设计中的核心问题:
- 责任链解决了行为请求的传递与处理问题。
- 工厂解决了对象创建的复杂性和依赖问题。
- 观察者解决了对象状态的同步与通知问题。
它们都深刻地体现了面向对象设计原则,尤其是依赖倒置原则(DIP)、开闭原则(OCP) 和单一职责原则(SRP)。熟练掌握这三种模式,是构建灵活、可维护、可扩展的Java应用程序的基石。本文通过超过5000字的详细讲解和代码示例,希望能帮助你彻底理解并掌握它们。