设计模式学习(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专栏


参考:

相关推荐
如果你想拥有什么先让自己配得上拥有1 小时前
向师傅学习的黄金和斐波总结二
学习
云边散步1 小时前
godot2D游戏教程系列一(8)
笔记·学习·音视频
楼田莉子1 小时前
CMake学习:入门及其下载配置
开发语言·c++·vscode·后端·学习
一条闲鱼_mytube2 小时前
智能体设计模式(六)资源感知优化-推理技术-评估与监控
网络·人工智能·设计模式
2501_901147832 小时前
组合总和IV——动态规划与高性能优化学习笔记
学习·算法·面试·职场和发展·性能优化·动态规划·求职招聘
Jan123.2 小时前
深入理解数据库事务与锁机制:InnoDB实战指南
数据库·学习
一条闲鱼_mytube2 小时前
智能体设计模式(七)优先级排序-探索与发现
网络·人工智能·设计模式
爱喝可乐的老王2 小时前
机器学习监督学习模型--决策树
学习·决策树·机器学习
人有一心2 小时前
【学习笔记】因果推理导论第4课
笔记·深度学习·学习