设计模式学习(19) 23-17 观察者模式

文章目录

  • 0.个人感悟
  • [1. 概念](#1. 概念)
  • [2. 适配场景](#2. 适配场景)
    • [2.1 适合的场景](#2.1 适合的场景)
    • [2.2 常见场景举例](#2.2 常见场景举例)
  • [3. 实现方法](#3. 实现方法)
    • [3.1 实现思路](#3.1 实现思路)
    • [3.2 UML类图](#3.2 UML类图)
    • [3.3 代码示例](#3.3 代码示例)
      • [3.3.1 自己实现](#3.3.1 自己实现)
      • [3.3.2 pro版:使用Java内置观察者模式java.util.Observer](#3.3.2 pro版:使用Java内置观察者模式java.util.Observer)
      • [3.3.3 plus版:java.beans.PropertyChangeSupport](#3.3.3 plus版:java.beans.PropertyChangeSupport)
  • [4. 优缺点](#4. 优缺点)
    • [4.1 优点](#4.1 优点)
    • [4.2 缺点](#4.2 缺点)
  • [5. 源码分析](#5. 源码分析)

0.个人感悟

  • 观察者模式,如名,希望建立对象之间的监听关系,使得被监听的信息能够通知到监听者
  • 工作中我们可能习惯于使用框架的监听功能或者消息三方件。学习这个模式让我有点溯源的感觉。
    • 提出模式,jdk 提供了java.util.Observable相关实现,可以满足基础需求
    • jdk9开始Observable废弃,引入PropertyChangeSupport,跟踪源码,明显更加灵活
    • 框架的诞生,出现了很多成熟的方案,比如spring的event
    • 分布式,多节点,除了kafka等三方件
  • 跟踪源码很有帮助,不仅可以帮助理解设计思路,同时有利于工作中快速使用api和定位bug(0.0)
  • 这篇博客写的有些久,主要是实例代码写了好几版,虽然可能还是没讲明白,不过敲的代码都是自己理解到的,希望对大家有帮助
  • 感谢优秀的博客,看到很多先驱者,感谢

1. 概念

英文定义 (《设计模式:可复用面向对象软件的基础》)

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

中文翻译

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

理解

  • 核心思想:建立一种发布-订阅机制
  • 解耦关键:被观察者(Subject)不知道观察者(Observer)的具体实现,只知道其接口
  • 通信方式:通过接口回调实现通知

2. 适配场景

2.1 适合的场景

  1. 事件驱动系统:当一个对象状态变化需要通知其他多个对象时
  2. GUI事件处理:如按钮点击、鼠标移动等事件的监听
  3. 数据监控:监控数据变化并通知相关组件更新
  4. 消息通知系统:如邮件订阅、价格变动提醒
  5. MVC架构:Model数据变化时自动更新View

2.2 常见场景举例

  • 电商系统:商品价格变化通知已收藏用户
  • 气象系统:气象站数据变化更新多个显示设备
  • 社交媒体:用户发布动态通知所有粉丝
  • 股票交易:股价变动通知所有订阅者
  • 物流系统:包裹状态变化通知相关方

3. 实现方法

3.1 实现思路

  1. 定义观察者接口:声明更新方法(update)
  2. 定义被观察者抽象类/接口:提供注册、移除和通知观察者的方法
  3. 实现具体被观察者:维护观察者列表,状态变化时通知所有观察者
  4. 实现具体观察者:实现更新方法,定义接收到通知后的行为
  5. 客户端使用:创建被观察者和观察者对象,建立订阅关系

3.2 UML类图

角色说明

  • Subject(被观察者):定义注册、移除和通知观察者的接口
  • ConcreteSubject(具体被观察者):实现Subject接口,维护观察者列表,状态变化时通知
  • Observer(观察者):定义更新接口
  • ConcreteObserver(具体观察者):实现Observer接口,定义具体更新逻辑

3.3 代码示例

以关注商品降价通知实现为例

3.3.1 自己实现

思路 :根据模式思路自己来实现

观察者接口

  • 定义通知(回调的方法) ,这里的msg也可以是约定的一个参数对象,这里便于演示,直接string
java 复制代码
public interface PriceObserver {  
  
    /**  
     * @param msg 消息  
     * @description 价格变化通知  
     * @author bigHao  
     * @date 2026/1/22  
     **/    
     void onPriceDrop(String msg);  
}

被观察者: 定义增删观察者的接口和通知接口

java 复制代码
public interface Subject {  
    /**  
     * @param priceObserver 观察者  
     * @description 添加观察者  
     * @author bigHao  
     * @date 2026/1/22  
     **/    
     void attach(PriceObserver priceObserver);  
  
    /**  
     * @param priceObserver 观察者  
     * @description 删除观察者  
     * @author bigHao  
     * @date 2026/1/22  
     **/    
     void detach(PriceObserver priceObserver);  
  
    /**  
     * @param msg 约定消息格式,比如json字符串  
     * @description 通知  
     * @author bigHao  
     * @date 2026/1/22  
     **/    
     void notifyObservers(String msg);  
}

具体被观察者: 商品。

  • 实现方法: 这里内置一个观察者列表
  • 添加自己触发通知的方法: 降价
java 复制代码
public class Product implements Subject {  
  
    private String name;  
  
    // 商品价格 被观察者state  
    private double price;  
  
    private List<PriceObserver> observers = new ArrayList<>();  
  
    public Product(String name, double price) {  
        this.name = name;  
        this.price = price;  
    }  
  
  
    @Override  
    public void attach(PriceObserver priceObserver) {  
        observers.add(priceObserver);  
        System.out.println();  
    }  
  
    @Override  
    public void detach(PriceObserver priceObserver) {  
        observers.remove(priceObserver);  
    }  
  
    @Override  
    public void notifyObservers(String msg) {  
        observers.forEach(item -> {  
            item.onPriceDrop(msg);  
        });  
    }  
  
    /**  
     * @param newPrice 新的价格  
     * @description 价格变化  
     * @author bigHao  
     * @date 2026/1/22  
     **/    
     public void updatePrice(double newPrice) {  
        if (newPrice < price) {  
            String msg = STR."\{name} 原价 \{price} 降价到 \{newPrice}";  
            System.out.println(msg);  
            this.price = newPrice;  
            notifyObservers(msg);  
        } else {  
            this.price = newPrice;  
        }  
    }  
}

具体被观察者:用户

java 复制代码
public class User implements PriceObserver {  
    private String name;  
  
    public User(String name) {  
        this.name = name;  
    }  
  
    @Override  
    public void onPriceDrop(String msg) {  
        // 这里可以解析msg 比如字符串转对象  
        System.out.println(STR."收到通知: \{msg} 笑醒了!");  
    }  
}

测试

java 复制代码
public class Client {  
    static void main() {  
        System.out.println("======自己实现====");  
        // 具体产品  
        Product rtx5090 = new Product("rtx5090",30000);  
  
        // 观察者  
        User observer1 = new User("Tom");  
  
        User observer2 = new User("Jack");  
  
        // 添加观察者  
        rtx5090.attach(observer1);  
        rtx5090.attach(observer2);  
  
        // 产品价格更新  
        System.out.println("=== 降价到23000 ===");  
        rtx5090.updatePrice(23000);  
  
        System.out.println("=== 降价到20000 ===");  
        rtx5090.updatePrice(20000);  
    }  
}

输出:

复制代码
======自己实现====


=== 降价到23000 ===
rtx5090 原价 30000.0 降价到 23000.0
收到通知: rtx5090 原价 30000.0 降价到 23000.0 笑醒了!
收到通知: rtx5090 原价 30000.0 降价到 23000.0 笑醒了!
=== 降价到20000 ===
rtx5090 原价 23000.0 降价到 20000.0
收到通知: rtx5090 原价 23000.0 降价到 20000.0 笑醒了!
收到通知: rtx5090 原价 23000.0 降价到 20000.0 笑醒了!

3.3.2 pro版:使用Java内置观察者模式java.util.Observer

思路 : java内置的观察者模式,其实思路和观察者模式一样,只是观察者和被观察者都集成到了sdk中。由于扩展性差,java9已过时,不过可以学习其思想

观察者:

  • update方法会回传被观察对象和参数
java 复制代码
package java.util;  
  

@Deprecated(since="9")  
public interface Observer {  
    void update(Observable o, Object arg);  
}

被观察者:

  • Vector持有观察者列表
  • 注意这个changed,用于这个类状态是否变化,在notifyObservers会用到着开关
java 复制代码
package java.util;  

@Deprecated(since="9")  
@SuppressWarnings("doclint:reference") // cross-module links  
public class Observable {  
    private boolean changed = false;  
    private Vector<Observer> obs;  
  
    public Observable() {  
        obs = new Vector<>();  
    }  
    public synchronized void addObserver(Observer o) {  
        if (o == null)  
            throw new NullPointerException();  
        if (!obs.contains(o)) {  
            obs.addElement(o);  
        }  
    }  
  
     public synchronized void deleteObserver(Observer o) {  
        obs.removeElement(o);  
    }  
  
     
    public void notifyObservers() {  
        notifyObservers(null);  
    }  
  
    public void notifyObservers(Object arg) {  
        Object[] arrLocal;  
        synchronized (this) {  
            if (!changed)  
                return;  
            arrLocal = obs.toArray();  
            clearChanged();  
        }  
  
        for (int i = arrLocal.length-1; i>=0; i--)  
            ((Observer)arrLocal[i]).update(this, arg);  
    }  
     
    public synchronized void deleteObservers() {  
        obs.removeAllElements();  
    }  
  
    protected synchronized void setChanged() {  
        changed = true;  
    }  
  
    protected synchronized void clearChanged() {  
        changed = false;  
    }  
  
    public synchronized boolean hasChanged() {  
        return changed;  
    }  
  
    public synchronized int countObservers() {  
        return obs.size();  
    }  
}

具体被观察者:

  • 注意调用 setChanged()
java 复制代码
public class Product extends Observable {  
    private String name;  
    private double price;  
  
    public Product(String name, double price) {  
        this.name = name;  
        this.price = price;  
    }  
  
    public void updatePrice(double newPrice) {  
        if (newPrice < price) {  
            String msg = STR."\{name} 原价 \{price} 降价到 \{newPrice}";  
            System.out.println(msg);  
            this.price = newPrice;  
  
            // 注意这里要设置下状态,因为notifyObservers中有个开关 if (!changed)  
            setChanged();  
            notifyObservers(msg);  
        } else {  
            this.price = newPrice;  
        }  
    }  
}

具体观察者:

java 复制代码
public class User implements Observer {  
    private String name;  
  
    public User(String name) {  
        this.name = name;  
    }  
  
    @Override  
    public void update(Observable o, Object arg) {  
        // 这里需要类型判断  
        if (o instanceof Product) {  
            Product product = (Product) o;  
            // 通知参数是object 需要转换  
            String msg = (String) arg;  
            System.out.println(STR."收到通知: \{msg} 笑醒了!");  
        }  
    }  
}

测试

java 复制代码
public class Client {  
    static void main() {  
        System.out.println("======jdk observer 实现====");  
        // 具体产品  
        Product rtx5090 = new Product("rtx5090",30000);  
  
        // 观察者  
        User observer1 = new User("Tom");  
  
        User observer2 = new User("Jack");  
  
  
        // 添加观察者  
        rtx5090.addObserver(observer1);  
        rtx5090.addObserver(observer2);  
  
        // 产品价格更新  
        System.out.println("=== 降价到23000 ===");  
        rtx5090.updatePrice(23000);  
  
        System.out.println("=== 降价到20000 ===");  
        rtx5090.updatePrice(20000);  
    }  
}

结果:

复制代码
======jdk observer 实现====
=== 降价到23000 ===
rtx5090 原价 30000.0 降价到 23000.0
收到通知: rtx5090 原价 30000.0 降价到 23000.0 笑醒了!
收到通知: rtx5090 原价 30000.0 降价到 23000.0 笑醒了!
=== 降价到20000 ===
rtx5090 原价 23000.0 降价到 20000.0
收到通知: rtx5090 原价 23000.0 降价到 20000.0 笑醒了!
收到通知: rtx5090 原价 23000.0 降价到 20000.0 笑醒了!

3.3.3 plus版:java.beans.PropertyChangeSupport

这是对观察者模式的扩展,更加灵活,扩展点在代码中会提到

整体设计思想

  • 事件封装成对象,包括足够信息,便于监听者处理数据
  • 支持指定主题监听-推送,也就是说除了监听对象外,还扩展了一层
    整体实现:
  • 引入PropertyChangeEvent封装事件参数,包括监听的对象、属性、新旧值等
  • 引入PropertyChangeSupport来协助监听。也就是说不需要继承被监听者,而是通过依赖的方式进行监听
  • 支持按propertyName监听,也就是说,可以只监听对象的个别属性,更加灵活。看后面的代码会更清晰,重点关注PropertyChangeSupport推送机制
    事件:
  • 封装事件参数,回调时甚至可以把监听的对象source返回来
java 复制代码
package java.beans;  
public class PropertyChangeEvent extends EventObject {

public PropertyChangeEvent(Object source, String propertyName,  
                           Object oldValue, Object newValue) {  
    super(source);  
    this.propertyName = propertyName;  
    this.newValue = newValue;  
    this.oldValue = oldValue;  
}
}  

观察者接口:

  • 传入PropertyChangeEvent,可以理解为观察者、被观察者的参数bean
java 复制代码
package java.beans;  
  
public interface PropertyChangeListener extends java.util.EventListener {  
    void propertyChange(PropertyChangeEvent evt);  
  
}

观察者support:

  • 不是继承它来使用,而是依赖它,更加灵活,构造方法PropertyChangeSupport(Object sourceBean),sourceBean即被观察对象,也是PropertyChangeEvent中的source
  • addPropertyChangeListener是重载方法,支持通用监听(监听所有事件)和按属性监听。具体实现是
    PropertyChangeListenerMap 类似 propertyName-List<listener>的结构,通用监听的key是null(所以map会有一个null key )
  • 构造事件,构建event对象,可以指定根据propertyName
  • 事件推送时,根据event.propertyName 和 map.key匹配,找到listeners进行推送
java 复制代码
public class PropertyChangeSupport implements Serializable {  
    private Object source;
    private PropertyChangeListenerMap map = new PropertyChangeListenerMap();
    
    // 构造函数
public PropertyChangeSupport(Object sourceBean) {  
    if (sourceBean == null) {  
        throw new NullPointerException();  
    }  
    source = sourceBean;  
}
    
// 通用监听,监听所有属性变化
public void addPropertyChangeListener(PropertyChangeListener listener) {  
    if (listener == null) {  
        return;  
    }  
    if (listener instanceof PropertyChangeListenerProxy) {  
        PropertyChangeListenerProxy proxy =  
               (PropertyChangeListenerProxy)listener;  
        // Call two argument add method.  
        addPropertyChangeListener(proxy.getPropertyName(),  
                                  proxy.getListener());  
    } else {  
        this.map.add(null, listener);  
    }  
}
 
// 按属性监听 
public void addPropertyChangeListener(  
            String propertyName,  
            PropertyChangeListener listener) {  
    if (listener == null || propertyName == null) {  
        return;  
    }  
    listener = this.map.extract(listener);  
    if (listener != null) {  
        this.map.add(propertyName, listener);  
    }  
}   

// 发起事件
public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {  
    if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {  
        firePropertyChange(new PropertyChangeEvent(this.source, propertyName, oldValue, newValue));  
    }  
} 

public void firePropertyChange(PropertyChangeEvent event) {  
    Object oldValue = event.getOldValue();  
    Object newValue = event.getNewValue();  
    if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {  
        String name = event.getPropertyName();  
  
        PropertyChangeListener[] common = this.map.get(null);  
        PropertyChangeListener[] named = (name != null)  
                    ? this.map.get(name)  
                    : null;  
  
        fire(common, event);  
        fire(named, event);  
    }  
}   
}

被监听者:

java 复制代码
public class Product {  
    public static final String PRICE = "price";  
  
    public static final String SELT_MSG = "selfMsg";  
  
    private String name;  
    private double price;  
  
    public Product(String name, double price) {  
        this.name = name;  
        this.price = price;  
    }  
  
    private final PropertyChangeSupport support = new PropertyChangeSupport(this);  
  
    public void attach(PropertyChangeListener listener) {  
        support.addPropertyChangeListener(listener);  
    }  
  
    public void attachPrice(PropertyChangeListener listener) {  
        support.addPropertyChangeListener(PRICE, listener);  
    }  
  
    public void attachSelfMsg(PropertyChangeListener listener) {  
        support.addPropertyChangeListener(SELT_MSG, listener);  
    }  
  
    public void detach(PropertyChangeListener listener) {  
        support.removePropertyChangeListener(listener);  
    }  
  
    public void updatePrice(double newPrice) {  
        if (newPrice < price) {  
            String msg = STR."\{name} 原价 \{price} 降价到 \{newPrice}";  
            System.out.println(msg);  
  
            // 这里会构建PropertyChangeEvent对象: new PropertyChangeEvent(this.source, propertyName, oldValue, newValue)  
  
            System.out.println("发送price事件");  
            support.firePropertyChange(PRICE, this.price, newPrice);  
  
            System.out.println("发送self事件");  
            support.firePropertyChange(SELT_MSG, null, msg);  
            this.price = newPrice;  
        } else {  
            this.price = newPrice;  
        }  
    }  
  
    @Override  
    public String toString() {  
        return STR."Product{name='\{name}', price=\{price}}";  
    }  
}

具体监听者: 实现PropertyChangeListener接口

java 复制代码
public class User implements PropertyChangeListener {  
    private String name;  
  
    public User(String name) {  
        this.name = name;  
    }  
  
    @Override  
    public void propertyChange(PropertyChangeEvent evt) {  
        String propertyName = evt.getPropertyName();  
        Object oldValue = evt.getOldValue();  
        Object newValue = evt.getNewValue();  
        Object source = evt.getSource();  
        Product product = (Product) source;  
        System.out.println(STR."\{name} 收到事件: ");  
        System.out.println("propertyName :" + propertyName);  
        System.out.println("oldValue :" + oldValue);  
        System.out.println("newValue :" + newValue);  
        System.out.println("product :" + product);  
    }  
}

测试:

java 复制代码
public class Client {  
    static void main() {  
        System.out.println("======jdk  PropertyChangeSupport实现====");  
        // 具体产品  
        Product rtx5090 = new Product("rtx5090", 30000);  
  
        User tom = new User("tom");  
        User jack = new User("jack");  
        User mary = new User("mary");  
  
        // tom监听有类型事件  
        rtx5090.attach(tom);  
  
        // jack监听price事件  
        rtx5090.attachPrice(jack);  
  
        // mary监听self事件  
        rtx5090.attachSelfMsg(mary);  
  
        // 这里会发送price和self事件  
        rtx5090.updatePrice(25000);  
    }  
}

结果

java 复制代码
public class Client {  
    static void main() {  
        System.out.println("======jdk  PropertyChangeSupport实现====");  
        // 具体产品  
        Product rtx5090 = new Product("rtx5090", 30000);  
  
        User tom = new User("tom");  
        User jack = new User("jack");  
        User mary = new User("mary");  
  
        // tom监听有类型事件  
        rtx5090.attach(tom);  
  
        // jack监听price事件  
        rtx5090.attachPrice(jack);  
  
        // mary监听self事件  
        rtx5090.attachSelfMsg(mary);  
  
        // 这里会发送price和self事件  
        rtx5090.updatePrice(25000);  
    }  
}

与传统方式对比

  • event参数化,更加灵活
  • 继承被观察者的方式改为依赖,耦合轻
  • 支持按propertyName监听

4. 优缺点

4.1 优点

  1. 高内聚低耦合

    • 被观察者和观察者松耦合,可以独立变化
    • 新增观察者无需修改被观察者代码
  2. 扩展性好

    • 可以动态添加/删除观察者
    • 支持广播通信,一对多通知
  3. 维护性高

    • 观察者逻辑独立,便于维护和测试
    • 支持运行时建立对象间关系
  4. 复用性强

    • Observer接口可被多种具体观察者实现
    • Subject可被多个不同场景复用

4.2 缺点

  1. 性能问题

    • 观察者过多时,通知开销大
    • 同步通知可能导致阻塞
  2. 稳定性风险循环依赖

    • 观察者间相互依赖可能导致循环调用
    • 观察者更新失败可能影响其他观察者

5. 源码分析

有兴趣可以看看spring事件,这里也留个坑,会开spring专栏


参考:

相关推荐
西岸行者3 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意3 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码3 天前
嵌入式学习路线
学习
毛小茛3 天前
计算机系统概论——校验码
学习
babe小鑫3 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms3 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下3 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。3 天前
2026.2.25监控学习
学习
im_AMBER3 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J3 天前
从“Hello World“ 开始 C++
c语言·c++·学习