Java设计模式(二):观察者模式与装饰器模式

简介: 本文深入讲解观察者模式与装饰器模式的核心概念及实现方式,涵盖从基础理论到实战应用的全面内容。观察者模式实现对象间松耦合通信,适用于事件通知机制;装饰器模式通过组合方式动态扩展对象功能,避免子类爆炸。文章通过Java示例展示两者在GUI、IO流、Web中间件等场景的应用,并提供常见陷阱与面试高频问题解析,助你写出灵活、可维护的代码。

💡 摘要:你是否需要实现对象间的动态通知机制?是否想在不修改原有类的情况下扩展功能?是否希望写出更灵活、更易维护的代码?

别担心,观察者模式和装饰器模式是两种极其有用的行为型和结构型设计模式。观察者模式帮你实现对象间的松耦合通信,装饰器模式让你能够动态地给对象添加新功能。

本文将带你从观察者模式的核心概念讲起,学习如何实现发布-订阅机制。然后深入装饰器模式,掌握如何通过组合而非继承来扩展功能。最后通过实战案例展示这两种模式在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开始,ObservableObserver被标记为过时,推荐使用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中哪些地方用了装饰器模式?

BufferedInputStreamDataInputStreamGZIPInputStream等都是装饰器,它们包装基础流添加额外功能。

❓4. 如何避免观察者模式中的内存泄漏?

:及时取消注册观察者,使用弱引用,或者在主题中使用CopyOnWriteArrayList等线程安全集合。

❓5. 装饰器模式如何保持开闭原则?

:装饰器模式允许在不修改现有代码的情况下扩展功能,符合开闭原则。可以通过添加新的装饰器来添加新功能。

相关文章
|
19天前
|
设计模式 Java Spring
Java 设计模式之责任链模式:优雅处理请求的艺术
责任链模式通过构建处理者链,使请求沿链传递直至被处理,实现发送者与接收者的解耦。适用于审批流程、日志处理等多级处理场景,提升系统灵活性与可扩展性。
147 2
|
19天前
|
设计模式 网络协议 数据可视化
Java 设计模式之状态模式:让对象的行为随状态优雅变化
状态模式通过封装对象的状态,使行为随状态变化而改变。以订单为例,将待支付、已支付等状态独立成类,消除冗长条件判断,提升代码可维护性与扩展性,适用于状态多、转换复杂的场景。
187 0
|
17天前
|
设计模式 算法 搜索推荐
Java 设计模式之策略模式:灵活切换算法的艺术
策略模式通过封装不同算法并实现灵活切换,将算法与使用解耦。以支付为例,微信、支付宝等支付方式作为独立策略,购物车根据选择调用对应支付逻辑,提升代码可维护性与扩展性,避免冗长条件判断,符合开闭原则。
190 35
|
17天前
|
设计模式 消息中间件 传感器
Java 设计模式之观察者模式:构建松耦合的事件响应系统
观察者模式是Java中常用的行为型设计模式,用于构建松耦合的事件响应系统。当一个对象状态改变时,所有依赖它的观察者将自动收到通知并更新。该模式通过抽象耦合实现发布-订阅机制,广泛应用于GUI事件处理、消息通知、数据监控等场景,具有良好的可扩展性和维护性。
167 8
|
6月前
|
设计模式 缓存 安全
【高薪程序员必看】万字长文拆解Java并发编程!(8):设计模式-享元模式设计指南
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的经典对象复用设计模式-享元模式,废话不多说让我们直接开始。
138 0
|
3月前
|
设计模式 安全 Java
Java设计模式(一):单例模式与工厂模式
本文详解单例模式与工厂模式的核心实现及应用,涵盖饿汉式、懒汉式、双重检查锁、工厂方法、抽象工厂等设计模式,并结合数据库连接池与支付系统实战案例,助你掌握设计模式精髓,提升代码专业性与可维护性。
|
3月前
|
设计模式 XML 安全
Java枚举(Enum)与设计模式应用
Java枚举不仅是类型安全的常量,还具备面向对象能力,可添加属性与方法,实现接口。通过枚举能优雅实现单例、策略、状态等设计模式,具备线程安全、序列化安全等特性,是编写高效、安全代码的利器。
|
11月前
|
设计模式 存储 供应链
前端必须掌握的设计模式——观察者模式
观察者模式(Observer Pattern)是一种行为型设计模式,实现了一种订阅机制。它包含两个角色:**观察者**(订阅消息、接收通知并执行操作)和**被观察者**(维护观察者列表、发送通知)。两者通过一对多的关系实现解耦,当被观察者状态改变时,会通知所有订阅的观察者。例如,商店老板作为被观察者,记录客户的需求并在商品到货时通知他们。前端应用中,如DOM事件注册、MutationObserver等也体现了这一模式。
|
6月前
|
设计模式 消息中间件 存储
【设计模式】【行为型模式】观察者模式(Observer)
一、入门 什么是观察者模式? 观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会收到通知并自动更新。
316 9
|
8月前
|
设计模式 Java 数据安全/隐私保护
Java 设计模式:装饰者模式(Decorator Pattern)
装饰者模式属于结构型设计模式,允许通过动态包装对象的方式为对象添加新功能,提供比继承更灵活的扩展方式。该模式通过组合替代继承,遵循开闭原则(对扩展开放,对修改关闭)。