【设计模式笔记26】:深入浅出「观察者模式」

文章目录

    • [1. 场景背景](#1. 场景背景)
    • [2. 代码实现](#2. 代码实现)
      • [2.1 抽象层:定义规约](#2.1 抽象层:定义规约)
      • [2.2 具体层:业务逻辑实现](#2.2 具体层:业务逻辑实现)
      • [2.3 客户端:模拟运行](#2.3 客户端:模拟运行)
    • [3. 模式结构解析](#3. 模式结构解析)
    • [4. JDK 中的观察者模式](#4. JDK 中的观察者模式)
      • [4.1 简单的对比](#4.1 简单的对比)
      • [4.2 JDK 9 废弃了 Observable](#4.2 JDK 9 废弃了 Observable)
      • [4.3 现在的推荐做法](#4.3 现在的推荐做法)
    • [5. 总结](#5. 总结)

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

在电商系统中,"商品降价通知"是典型的观察者模式应用场景。今天我们以 "华为Mate 60 Pro 价格变动通知会员" 为例,通过 Java 代码手写一个观察者模式,并深入探讨其背后的设计哲学。


1. 场景背景

假设我们经营一家 OnlineStore (在线商店)

  • 被观察者 (Subject):热门商品(如华为 Mate 60 Pro)。
  • 观察者 (Observer):关注该商品的会员(张三、李四、王五)。
  • 需求:当商品价格发生变动时,系统需自动通知所有关注该商品的会员。

2. 代码实现

我们将代码分为三个部分:接口层、具体实现层、业务测试层。

2.1 抽象层:定义规约

首先,我们需要定义"主题"和"观察者"的接口,利用接口来解耦。

java 复制代码
package com.demo.observer.interfaces;

import com.demo.observer.concretes.Product;

/**
 * 观察者接口 (IMember)
 * 定义接收通知的统一方法
 */
public interface IMember {
    void update(Product product);  // 接收商品状态变化的通知
}
java 复制代码
package com.demo.observer.interfaces;

/**
 * 主题接口 (IProduct)
 * 定义管理观察者(添加、移除、通知)的方法
 */
public interface IProduct {
    void attach(IMember member);    // 添加关注者
    void detach(IMember member);    // 移除关注者
    void notifyObservers();         // 通知所有关注者
}

2.2 具体层:业务逻辑实现

具体主题 (ConcreteSubject):商品类,持有观察者列表。

java 复制代码
package com.demo.observer.concretes;

import com.demo.observer.interfaces.IMember;
import com.demo.observer.interfaces.IProduct;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;

@Getter
public class Product implements IProduct {
    // 核心:维护一个观察者列表
    private List<IMember> members = new ArrayList<>(); 
    private String name;  
    private double price; 

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    // 当价格发生变化时,这是触发点
    public void setPrice(double price) {
        System.out.println("\n[系统消息]:商品 '" + this.name + "' 价格正在从 " + this.price + " 更新为 " + price);
        this.price = price;
        // 关键一步:状态改变后,立即通知观察者
        this.notifyObservers();
    }

    @Override
    public void attach(IMember member) {
        if (!members.contains(member)) {
            members.add(member);
        }
    }

    @Override
    public void detach(IMember member) {
        members.remove(member);
    }

    @Override
    public void notifyObservers() {
        // 遍历通知所有关注该商品的会员
        for (IMember member : members) {
            member.update(this); // 将当前商品对象传给观察者
        }
    }
}

具体观察者 (ConcreteObserver):会员类,实现具体的响应逻辑。

java 复制代码
package com.demo.observer.observers;

import com.demo.observer.interfaces.IMember;
import com.demo.observer.concretes.Product;

public class Member implements IMember {
    private String name; 

    public Member(String name) {
        this.name = name;
    }

    @Override
    public void update(Product product) {
        // 响应逻辑:收到通知后的具体操作
        System.out.println("   [通知] -> 尊敬的会员 " + this.name + ":");
        System.out.println("       您关注的商品 '" + product.getName() + "' 价格已更新为 " + product.getPrice() + "元!");
    }
}

2.3 客户端:模拟运行

java 复制代码
package com.demo.observer;

import com.demo.observer.concretes.Product;
import com.demo.observer.interfaces.IMember;
import com.demo.observer.observers.Member;

public class OnlineStore {
    public static void main(String[] args) {
        // 1. 创建被观察者(商品)
        Product phone = new Product("华为Mate 60 Pro", 6999.00);

        // 2. 创建观察者(会员)
        IMember memberZhang = new Member("张三");
        IMember memberLi = new Member("李四");
        IMember memberWang = new Member("王五");

        // 3. 建立订阅关系
        phone.attach(memberZhang);
        phone.attach(memberLi);
        System.out.println("--- 张三和李四关注了手机 ---");

        // 4. 触发事件:商品降价
        phone.setPrice(6499.00);

        // 5. 动态调整订阅关系
        System.out.println("\n--- 王五关注了手机,李四取消了关注 ---");
        phone.attach(memberWang);
        phone.detach(memberLi);

        // 6. 再次触发事件:商品涨价
        phone.setPrice(6699.00);
    }
}

运行结果:

text 复制代码
--- 张三和李四关注了手机 ---

 [系统消息]:商品 '华为Mate 60 Pro' 价格正在从 6999.0 更新为 6499.0
    [通知] -> 尊敬的会员 张三:
       您关注的商品 '华为Mate 60 Pro' 价格已更新为 6499.0元!
    [通知] -> 尊敬的会员 李四:
       您关注的商品 '华为Mate 60 Pro' 价格已更新为 6499.0元!

--- 王五关注了手机,李四取消了关注 ---

 [系统消息]:商品 '华为Mate 60 Pro' 价格正在从 6499.0 更新为 6699.0
   [通知] -> 尊敬的会员 张三:
       您关注的商品 '华为Mate 60 Pro' 价格已更新为 6699.0元!
   [通知] -> 尊敬的会员 王五:
       您关注的商品 '华为Mate 60 Pro' 价格已更新为 6699.0元!

3. 模式结构解析

UML 类图

实现
实现
聚合 (持有列表)
<<interface>>
IProduct
+attach(IMember)
+detach(IMember)
+notifyObservers()
Product
-List<IMember> members
-String name
-double price
+setPrice(double)
+attach(IMember)
+detach(IMember)
+notifyObservers()
<<interface>>
IMember
+update(Product)
Member
-String name
+update(Product)

核心工作流程

  1. 注册 (Attach)Product 内部维护一个 List<IMember>
  2. 状态变更 :当 setPrice() 被调用,商品价格改变。
  3. 触发通知setPrice() 内部调用 notifyObservers()
  4. 广播notifyObservers() 遍历列表,依次调用每个会员的 update() 方法。

4. JDK 中的观察者模式

在 Java 的早期版本(JDK 1.0)中,官方提供了 java.util.Observable 类和 java.util.Observer 接口来实现此模式。

4.1 简单的对比

特性 我们实现的模式 (Interface方案) JDK 原生模式 (Class继承方案)
被观察者类型 接口 IProduct Observable
观察者类型 接口 IMember 接口 Observer
通知机制 列表遍历 + 自定义方法 setChanged() + notifyObservers()
灵活性 (实现接口,不占继承位) (必须继承 Observable 类)

4.2 JDK 9 废弃了 Observable

如果你查看 JDK 9+ 的文档,会发现 Observable 被标记为 @Deprecated。原因主要有三点:

  1. 继承的局限性Observable 是一个 而不是接口。Java 是单继承的,如果你的业务类(如 Product)已经继承了别的父类(比如 BaseEntity),就无法再继承 Observable,这限制了复用性。
  2. 线程安全问题Observable 内部使用 Vector 来存储观察者,虽然 Vector 是线程安全的,但在高并发场景下,锁的粒度较大,且关于"如果在通知过程中有观察者被移除会发生什么"的保护机制不够灵活。
  3. 无法序列化Observable 没有实现 Serializable 接口。

4.3 现在的推荐做法

如果不想自己手写观察者模式,可以使用以下替代方案:

  • Java Beansjava.beans.PropertyChangeSupport(适合属性监听)。
  • Java 9 Flow APIjava.util.concurrent.Flow(响应式编程,支持背压)。
  • 第三方库 :Guava 的 EventBus 或 Spring 的 ApplicationEvent(发布-订阅模型)。

5. 总结

优点

  • 解耦合Product 不需要知道 Member 的具体实现,只知道它实现了 IMember 接口。
  • 开闭原则 :如果以后增加了新的观察者类型(比如"管理员通知"),只需实现 IMember 接口,无需修改 Product 的代码。
  • 动态性:可以在运行时随意添加或删除观察者。

缺点

  • 循环依赖:如果观察者和被观察者互相引用,可能导致死循环。
  • 性能问题 :如果观察者非常多,遍历通知会比较耗时(可以通过异步通知解决)。

一句话总结

观察者模式就是 "别给我打电话,有事我会通知你" 的典型体现,它是实现系统组件解耦的利器

相关推荐
Noushiki2 小时前
RabbitMQ 基础 学习笔记1
笔记·学习·rabbitmq
会算数的⑨2 小时前
Java场景化面经分享(一)—— JVM有关
java·开发语言·jvm·后端·面试
lpfasd1232 小时前
Spring Boot 4.0 新特性全解析 + 实操指南
java·spring boot·后端
葵花楹2 小时前
【JAVA期末复习】
java·开发语言·排序算法
冷雨夜中漫步2 小时前
Kubernetes入门笔记 ——(4)Windows搭建k8s测试集群
windows·笔记·kubernetes
我命由我123452 小时前
Photoshop - Photoshop 工具栏(53)画板工具
笔记·学习·职场和发展·求职招聘·职场发展·学习方法·photoshop
3824278272 小时前
Edge开发者工具:保留日志与禁用缓存详解
java·前端·javascript·python·selenium
QT 小鲜肉2 小时前
【Linux命令大全】001.文件管理之whereis命令(实操篇)
linux·运维·服务器·网络·笔记
m0_598177232 小时前
SQL(5)- 事务
java·数据库·sql