💡 摘要:你是否需要实现对象间的动态通知机制?是否想在不修改原有类的情况下扩展功能?是否希望写出更灵活、更易维护的代码?
别担心,观察者模式和装饰器模式是两种极其有用的行为型和结构型设计模式。观察者模式帮你实现对象间的松耦合通信,装饰器模式让你能够动态地给对象添加新功能。
本文将带你从观察者模式的核心概念讲起,学习如何实现发布-订阅机制。然后深入装饰器模式,掌握如何通过组合而非继承来扩展功能。最后通过实战案例展示这两种模式在GUI开发、IO流处理、事件系统等场景的实际应用。从基础实现到高级技巧,从Java内置支持到自定义实现,让你全面掌握这两种强大的设计模式。文末附常见陷阱和面试高频问题,助你写出更专业的代码。
一、观察者模式:优雅的事件通知机制
1. 观察者模式核心概念
观察者模式定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
核心角色:
Subject(主题):被观察的对象,维护观察者列表,提供注册和通知方法Observer(观察者):观察主题的对象,定义更新接口ConcreteSubject(具体主题):具体被观察对象,存储状态,状态改变时通知观察者ConcreteObserver(具体观察者):具体观察者,实现更新接口
2. 自定义观察者模式实现
基础实现:
java
import java.util.ArrayList;
import java.util.List;
// 观察者接口
interface Observer {
void update(String message);
}
// 主题接口
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// 具体主题:新闻发布中心
class NewsAgency implements Subject {
private List<Observer> observers = new ArrayList<>();
private String news;
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(news);
}
}
public void setNews(String news) {
this.news = news;
notifyObservers(); // 新闻更新时通知所有观察者
}
}
// 具体观察者:新闻订阅者
class NewsSubscriber implements Observer {
private String name;
public NewsSubscriber(String name) {
this.name = name;
}
@Override
public void update(String news) {
System.out.println(name + " 收到新闻: " + news);
}
}
// 使用示例
public class ObserverDemo {
public static void main(String[] args) {
NewsAgency agency = new NewsAgency();
// 创建订阅者
Observer subscriber1 = new NewsSubscriber("张三");
Observer subscriber2 = new NewsSubscriber("李四");
Observer subscriber3 = new NewsSubscriber("王五");
// 注册观察者
agency.registerObserver(subscriber1);
agency.registerObserver(subscriber2);
agency.registerObserver(subscriber3);
// 发布新闻,自动通知所有订阅者
agency.setNews("Java 21正式发布!");
// 输出:
// 张三 收到新闻: Java 21正式发布!
// 李四 收到新闻: Java 21正式发布!
// 王五 收到新闻: Java 21正式发布!
// 取消一个订阅者
agency.removeObserver(subscriber2);
// 再次发布新闻
agency.setNews("Spring Boot 3.0发布");
// 输出:
// 张三 收到新闻: Spring Boot 3.0发布
// 王五 收到新闻: Spring Boot 3.0发布
}
}
3. Java内置观察者支持
java.util.Observable 和 java.util.Observer:
java
import java.util.Observable;
import java.util.Observer;
// 使用Java内置的Observable类
class WeatherStation extends Observable {
private float temperature;
private float humidity;
private float pressure;
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
setChanged(); // 标记状态已改变
notifyObservers(); // 通知观察者
}
public float getTemperature() { return temperature; }
public float getHumidity() { return humidity; }
public float getPressure() { return pressure; }
}
// 使用Java内置的Observer接口
class WeatherDisplay implements Observer {
private String name;
public WeatherDisplay(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherStation) {
WeatherStation station = (WeatherStation) o;
System.out.printf("%s: 温度=%.1f°C, 湿度=%.1f%%, 气压=%.1fhPa%n",
name, station.getTemperature(), station.getHumidity(), station.getPressure());
}
}
}
// 使用示例
public class JavaBuiltInObserver {
public static void main(String[] args) {
WeatherStation station = new WeatherStation();
WeatherDisplay display1 = new WeatherDisplay("显示设备1");
WeatherDisplay display2 = new WeatherDisplay("显示设备2");
station.addObserver(display1);
station.addObserver(display2);
// 更新气象数据,自动通知所有显示设备
station.setMeasurements(25.5f, 65.0f, 1013.2f);
// 输出:
// 显示设备2: 温度=25.5°C, 湿度=65.0%, 气压=1013.2hPa
// 显示设备1: 温度=25.5°C, 湿度=65.0%, 气压=1013.2hPa
}
}
注意:Java 9开始,Observable和Observer被标记为过时,推荐使用PropertyChangeListener等更现代的实现。
4. 现代观察者模式实现
使用PropertyChangeSupport:
java
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
class ModernWeatherStation {
private final PropertyChangeSupport support = new PropertyChangeSupport(this);
private float temperature;
private float humidity;
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
support.removePropertyChangeListener(listener);
}
public void setMeasurements(float temperature, float humidity) {
float oldTemp = this.temperature;
float oldHumidity = this.humidity;
this.temperature = temperature;
this.humidity = humidity;
// 触发属性变更事件
support.firePropertyChange("temperature", oldTemp, temperature);
support.firePropertyChange("humidity", oldHumidity, humidity);
}
}
class ModernDisplay implements PropertyChangeListener {
@Override
public void propertyChange(java.beans.PropertyChangeEvent evt) {
System.out.printf("属性 %s 从 %s 变为 %s%n",
evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
}
}
二、装饰器模式:动态扩展功能
1. 装饰器模式核心概念
装饰器模式定义:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。
核心角色:
Component(组件):定义对象的接口,可以给这些对象动态添加职责ConcreteComponent(具体组件):定义具体的对象Decorator(装饰器):持有一个Component对象的引用,并实现Component接口ConcreteDecorator(具体装饰器):向组件添加具体的职责
2. 自定义装饰器模式实现
咖啡店示例:
java
// 组件接口:饮料
interface Beverage {
String getDescription();
double getCost();
}
// 具体组件:浓缩咖啡
class Espresso implements Beverage {
@Override
public String getDescription() {
return "浓缩咖啡";
}
@Override
public double getCost() {
return 12.0;
}
}
// 具体组件:拿铁
class Latte implements Beverage {
@Override
public String getDescription() {
return "拿铁";
}
@Override
public double getCost() {
return 15.0;
}
}
// 装饰器抽象类
abstract class CondimentDecorator implements Beverage {
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
@Override
public abstract String getDescription();
@Override
public abstract double getCost();
}
// 具体装饰器:牛奶
class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + " + 牛奶";
}
@Override
public double getCost() {
return beverage.getCost() + 3.0;
}
}
// 具体装饰器:糖
class Sugar extends CondimentDecorator {
public Sugar(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + " + 糖";
}
@Override
public double getCost() {
return beverage.getCost() + 1.0;
}
}
// 具体装饰器:奶油
class Cream extends CondimentDecorator {
public Cream(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + " + 奶油";
}
@Override
public double getCost() {
return beverage.getCost() + 4.0;
}
}
// 使用示例
public class DecoratorDemo {
public static void main(String[] args) {
// 点一杯浓缩咖啡
Beverage espresso = new Espresso();
System.out.println(espresso.getDescription() + " 价格: " + espresso.getCost());
// 输出:浓缩咖啡 价格: 12.0
// 加牛奶
Beverage espressoWithMilk = new Milk(espresso);
System.out.println(espressoWithMilk.getDescription() + " 价格: " + espressoWithMilk.getCost());
// 输出:浓缩咖啡 + 牛奶 价格: 15.0
// 加牛奶和糖
Beverage espressoWithMilkAndSugar = new Sugar(espressoWithMilk);
System.out.println(espressoWithMilkAndSugar.getDescription() + " 价格: " + espressoWithMilkAndSugar.getCost());
// 输出:浓缩咖啡 + 牛奶 + 糖 价格: 16.0
// 再加奶油
Beverage deluxeCoffee = new Cream(espressoWithMilkAndSugar);
System.out.println(deluxeCoffee.getDescription() + " 价格: " + deluxeCoffee.getCost());
// 输出:浓缩咖啡 + 牛奶 + 糖 + 奶油 价格: 20.0
// 另一杯:拿铁加奶油
Beverage latteWithCream = new Cream(new Latte());
System.out.println(latteWithCream.getDescription() + " 价格: " + latteWithCream.getCost());
// 输出:拿铁 + 奶油 价格: 19.0
}
}
3. Java IO中的装饰器模式
Java IO流的装饰器应用:
java
import java.io.*;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class IODecoratorExample {
public static void main(String[] args) {
try {
// 基础组件:文件输出流
FileOutputStream fileOutput = new FileOutputStream("test.txt");
// 装饰器:缓冲流(添加缓冲功能)
BufferedOutputStream bufferedOutput = new BufferedOutputStream(fileOutput);
// 装饰器:数据输出流(添加数据类型写入功能)
DataOutputStream dataOutput = new DataOutputStream(bufferedOutput);
// 使用装饰后的流
dataOutput.writeUTF("Hello, Decorator Pattern!");
dataOutput.writeInt(42);
dataOutput.writeDouble(3.14);
dataOutput.close();
// 读取时同样使用装饰器模式
FileInputStream fileInput = new FileInputStream("test.txt");
BufferedInputStream bufferedInput = new BufferedInputStream(fileInput);
DataInputStream dataInput = new DataInputStream(bufferedInput);
System.out.println("读取字符串: " + dataInput.readUTF());
System.out.println("读取整数: " + dataInput.readInt());
System.out.println("读取浮点数: " + dataInput.readDouble());
dataInput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 自定义装饰器示例:加密输出流
class CryptoOutputStream extends FilterOutputStream {
private final byte[] key;
private int keyIndex;
public CryptoOutputStream(OutputStream out, byte[] key) {
super(out);
this.key = key;
this.keyIndex = 0;
}
@Override
public void write(int b) throws IOException {
// 简单的异或加密
int encrypted = b ^ key[keyIndex];
keyIndex = (keyIndex + 1) % key.length;
super.write(encrypted);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
for (int i = off; i < off + len; i++) {
write(b[i]);
}
}
}
三、实战应用案例
1. GUI事件处理(观察者模式)
Swing中的观察者模式:
java
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class GUIObserverExample {
public static void main(String[] args) {
JFrame frame = new JFrame("观察者模式示例");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 200);
JButton button = new JButton("点击我!");
JLabel label = new JLabel("等待点击...");
// 注册观察者(ActionListener就是观察者接口)
button.addActionListener(new ActionListener() {
private int clickCount = 0;
@Override
public void actionPerformed(ActionEvent e) {
clickCount++;
label.setText("按钮被点击了 " + clickCount + " 次");
}
});
// 可以注册多个观察者
button.addActionListener(e -> {
System.out.println("按钮在 " + System.currentTimeMillis() + " 被点击");
});
frame.getContentPane().add(button, "North");
frame.getContentPane().add(label, "South");
frame.setVisible(true);
}
}
2. Web中间件装饰器
HTTP请求处理装饰器:
java
// 组件接口:请求处理器
interface RequestHandler {
void handleRequest(HttpRequest request, HttpResponse response);
}
// 具体组件:基础请求处理器
class BasicRequestHandler implements RequestHandler {
@Override
public void handleRequest(HttpRequest request, HttpResponse response) {
System.out.println("处理基础请求: " + request.getPath());
response.setStatus(200);
response.setBody("Hello World");
}
}
// 装饰器:认证装饰器
class AuthenticationDecorator implements RequestHandler {
private RequestHandler handler;
public AuthenticationDecorator(RequestHandler handler) {
this.handler = handler;
}
@Override
public void handleRequest(HttpRequest request, HttpResponse response) {
if (isAuthenticated(request)) {
handler.handleRequest(request, response);
} else {
response.setStatus(401);
response.setBody("未授权");
}
}
private boolean isAuthenticated(HttpRequest request) {
String token = request.getHeader("Authorization");
return token != null && token.startsWith("Bearer ");
}
}
// 装饰器:日志装饰器
class LoggingDecorator implements RequestHandler {
private RequestHandler handler;
public LoggingDecorator(RequestHandler handler) {
this.handler = handler;
}
@Override
public void handleRequest(HttpRequest request, HttpResponse response) {
long startTime = System.currentTimeMillis();
handler.handleRequest(request, response);
long endTime = System.currentTimeMillis();
System.out.printf("请求 %s 处理完成,状态码: %d,耗时: %dms%n",
request.getPath(), response.getStatus(), endTime - startTime);
}
}
// 装饰器:缓存装饰器
class CachingDecorator implements RequestHandler {
private RequestHandler handler;
private Map<String, CacheEntry> cache = new HashMap<>();
public CachingDecorator(RequestHandler handler) {
this.handler = handler;
}
@Override
public void handleRequest(HttpRequest request, HttpResponse response) {
String cacheKey = request.getPath();
CacheEntry entry = cache.get(cacheKey);
if (entry != null && !entry.isExpired()) {
// 返回缓存响应
response.setStatus(entry.getStatus());
response.setBody(entry.getBody());
response.setHeader("X-Cache", "HIT");
} else {
// 处理请求并缓存结果
handler.handleRequest(request, response);
cache.put(cacheKey, new CacheEntry(response, System.currentTimeMillis()));
response.setHeader("X-Cache", "MISS");
}
}
}
// 使用示例
public class WebMiddlewareExample {
public static void main(String[] args) {
// 创建基础处理器
RequestHandler handler = new BasicRequestHandler();
// 添加装饰器:认证 → 日志 → 缓存
handler = new AuthenticationDecorator(handler);
handler = new LoggingDecorator(handler);
handler = new CachingDecorator(handler);
// 处理请求
HttpRequest request = new HttpRequest("/api/data", "GET");
request.setHeader("Authorization", "Bearer token123");
HttpResponse response = new HttpResponse();
handler.handleRequest(request, response);
System.out.println("最终响应: " + response.getBody());
}
}
四、模式对比与选择指南
1. 观察者模式 vs 发布-订阅模式
| 特性 | 观察者模式 | 发布-订阅模式 |
| 耦合度 | 紧耦合(直接引用) | 松耦合(通过中间件) |
| 通信方式 | 直接调用 | 通过消息队列/主题 |
| 灵活性 | 较低 | 较高 |
| 适用场景 | 对象间直接通知 | 分布式系统,跨进程通信 |
2. 装饰器模式 vs 继承
| 特性 | 装饰器模式 | 继承 |
| 扩展方式 | 动态组合 | 静态编译 |
| 灵活性 | 高(运行时改变) | 低(编译时确定) |
| 类数量 | 较多装饰器类 | 较多子类 |
| 原则遵循 | 开闭原则、组合复用 | 继承 |
五、常见陷阱与最佳实践
1. 观察者模式陷阱
避免观察者处理过慢:
java
class EfficientSubject implements Subject {
private List<Observer> observers = new CopyOnWriteArrayList<>(); // 线程安全且避免并发修改
public void notifyObserversAsync(String message) {
// 异步通知,避免阻塞主题
CompletableFuture.runAsync(() -> {
for (Observer observer : observers) {
try {
observer.update(message);
} catch (Exception e) {
// 处理异常,避免影响其他观察者
System.err.println("观察者处理失败: " + e.getMessage());
}
}
});
}
}
2. 装饰器模式最佳实践
保持接口一致性:
java
abstract class StrictDecorator implements Beverage {
protected final Beverage beverage;
public StrictDecorator(Beverage beverage) {
if (beverage == null) {
throw new IllegalArgumentException("被装饰对象不能为null");
}
this.beverage = beverage;
}
// 确保所有装饰器都实现所有方法
@Override
public String getDescription() {
return beverage.getDescription();
}
@Override
public double getCost() {
return beverage.getCost();
}
}
六、总结:模式选择指南
1. 观察者模式使用场景
- ✅ GUI事件处理
- ✅ 消息通知系统
- ✅ 实时数据监控
- ✅ 事件驱动架构
- ✅ 发布-订阅系统
2. 装饰器模式使用场景
- ✅ IO流处理
- ✅ Web中间件链
- ✅ 动态功能扩展
- ✅ 替代子类爆炸
- ✅ 权限控制和日志记录
七、面试高频问题
❓1. 观察者模式和发布-订阅模式有什么区别?
答:观察者模式是直接通信,耦合度较高;发布-订阅模式通过中间件通信,耦合度较低,更适合分布式系统。
❓2. 装饰器模式和代理模式有什么区别?
答:装饰器模式关注动态添加功能,代理模式关注控制访问。装饰器通常对用户透明,代理通常代表另一个对象。
❓3. Java IO中哪些地方用了装饰器模式?
答:BufferedInputStream、DataInputStream、GZIPInputStream等都是装饰器,它们包装基础流添加额外功能。
❓4. 如何避免观察者模式中的内存泄漏?
答:及时取消注册观察者,使用弱引用,或者在主题中使用CopyOnWriteArrayList等线程安全集合。
❓5. 装饰器模式如何保持开闭原则?
答:装饰器模式允许在不修改现有代码的情况下扩展功能,符合开闭原则。可以通过添加新的装饰器来添加新功能。