设计模式-观察着模式

观察者模式

观察者模式 (Observer Pattern) 是一种行为型设计模式,它定义了对象之间一种一对多的依赖关系,当一个对象(称为主题或可观察者)的状态发生改变时,所有依赖于它的对象(称为观察者)都会得到通知并自动更新。

核心思想:

想象一下报纸订阅的场景:

  • 报社 (Subject/Observable):负责出版报纸。

  • 订阅者 (Observer):对报社的报纸感兴趣的人。

当报社出版了新一期的报纸(状态改变),它会通知所有订阅了这份报纸的订阅者。订阅者收到通知后,就可以去获取最新的报纸内容进行阅读(自动更新)。

观察者模式就是这样,它允许一个对象(主题)在状态改变时通知其他多个对象(观察者),而无需知道这些观察者的具体细节。

模式结构:

观察者模式通常包含以下角色:

  1. 主题 (Subject) / 可观察者 (Observable)

    • 维护一个观察者列表。

    • 提供用于注册 (attach/subscribe) 和注销 (detach/unsubscribe) 观察者的方法。

    • 当其自身状态发生变化时,负责通知所有已注册的观察者。

    • 通常是一个接口或抽象类,定义了上述方法。

  2. 具体主题 (ConcreteSubject)

    • 实现了主题接口/继承了抽象主题类。

    • 存储了具体的状态,当状态改变时,会通知所有注册的观察者。

    • 是实际的"被观察"对象。

  3. 观察者 (Observer)

    • 定义了一个更新接口 (通常是一个名为 update() 的方法),当接收到主题的通知时,该方法被调用。

    • 通常是一个接口或抽象类。

  4. 具体观察者 (ConcreteObserver)

    • 实现了观察者接口/继承了抽象观察者类。

    • 维护一个指向具体主题对象的引用(或者在 update() 方法中接收主题对象或其状态作为参数),以便在收到更新通知时可以获取主题的状态。

    • 实现 update() 方法,根据主题状态的改变来执行相应的操作。

示意图:

复制代码
+---------------------+       maintains a list of       +--------------------+
|       Subject       |<--------------------------------|      Observer      |
| (Observable)        |                                 | (e.g., update())   |
+---------------------+                                 +--------------------+
        ^                                                        ^
        | implements                                             | implements
+---------------------+                                 +--------------------+
|  ConcreteSubject  |--------------------------------->| ConcreteObserverA  |
| - state             |         notifies                +--------------------+
| - registerObserver()|                                 +--------------------+
| - removeObserver()  |                                 | ConcreteObserverB  |
| - notifyObservers() |                                 +--------------------+
+---------------------+

工作流程:

  1. 具体观察者对象将自己注册到具体主题对象中。

  2. 当具体主题对象的状态发生改变时,它会调用其 notifyObservers() 方法。

  3. notifyObservers() 方法会遍历已注册的观察者列表,并调用每个观察者的 update() 方法。

  4. 具体观察者在其 update() 方法中,通常会从主题对象那里获取最新的状态,并根据这个状态执行相应的更新操作。

优点:

  1. 松耦合 (Loose Coupling)

    • 主题和观察者之间是松耦合的。 主题只知道它有一系列观察者(实现了某个接口),但不需要知道观察者的具体类型或它们如何处理通知。

    • 观察者之间也是松耦合的。 它们各自独立地响应主题的变化。

  2. 易于扩展

    • 可以很容易地增加新的观察者,而无需修改主题的代码。

    • 也可以很容易地增加新的主题类型。

  3. 支持广播通信:主题可以向所有注册的观察者广播状态变化。

  4. 符合开闭原则:对扩展开放(可以增加新的观察者),对修改关闭(通常不需要修改现有主题或观察者的核心代码来添加新的观察者)。

缺点:

  1. 可能导致意外的更新:如果一个主题有大量的观察者,或者观察者的更新逻辑比较复杂,通知和更新过程可能会比较耗时。

  2. 更新顺序问题:在某些情况下,观察者接收通知的顺序可能很重要,但标准的观察者模式通常不保证特定的通知顺序。如果需要顺序,可能需要额外的逻辑或对模式进行扩展。

  3. 内存泄漏风险:如果观察者在不再需要时没有从主题中正确注销,主题可能会一直持有对这些观察者的引用,导致它们无法被垃圾回收,从而引发内存泄漏(特别是在观察者生命周期比主题短的情况下)。

  4. 级联更新 (Cascading Updates):一个观察者的更新操作可能会触发另一个观察者的更新,甚至可能导致循环依赖和无限更新(需要小心设计)。

适用场景:

  1. 当一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变。

  2. 当一个对象必须通知其他对象,而它又不能假定其他对象是谁。换言之,不希望这些对象是紧密耦合的。

  3. 在GUI事件处理中非常常见(例如,按钮点击事件,模型-视图-控制器 (MVC) 架构中的模型和视图之间的交互)。

  4. 消息队列、发布/订阅系统。

  5. 当一个抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。

代码示例 (Java - 使用内置的 java.util.Observable 和 java.util.Observer,虽然它们在 Java 9 中被标记为 @Deprecated,但原理相同,也可以自己实现):

自己实现一个简单的观察者模式:

Subject (接口):

复制代码
import java.util.ArrayList;
import java.util.List;
​
// 主题接口
interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

Observer (接口):

复制代码
// 观察者接口
interface Observer {
    void update(String message); // 简化:直接传递消息
}

ConcreteSubject:

复制代码
class ConcreteSubject implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private String message;
​
    public void setMessage(String message) {
        this.message = message;
        System.out.println("Subject: State changed to '" + message + "'");
        notifyObservers(); // 状态改变时通知观察者
    }
​
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }
​
    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }
​
    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}

ConcreteObserver:

复制代码
class ConcreteObserver implements Observer {
    private String name;
​
    public ConcreteObserver(String name) {
        this.name = name;
    }
​
    @Override
    public void update(String message) {
        System.out.println(name + " received update: '" + message + "'");
    }
}

客户端代码:

复制代码
public class ObserverDemo {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
​
        ConcreteObserver observer1 = new ConcreteObserver("Observer A");
        ConcreteObserver observer2 = new ConcreteObserver("Observer B");
​
        // 注册观察者
        subject.registerObserver(observer1);
        subject.registerObserver(observer2);
​
        // 主题状态改变
        subject.setMessage("Hello Observers!");
​
        System.out.println("\n--- Observer A unsubscribes ---");
        subject.removeObserver(observer1);
​
        // 主题状态再次改变
        subject.setMessage("Another update!");
    }
}

输出:

复制代码
Subject: State changed to 'Hello Observers!'
Observer A received update: 'Hello Observers!'
Observer B received update: 'Hello Observers!'
​
--- Observer A unsubscribes ---
Subject: State changed to 'Another update!'
Observer B received update: 'Another update!'

关于 Java 内置的 Observable 和 Observer:

  • java.util.Observable 是一个类,而不是接口,这意味着具体主题必须继承它,限制了其复用性(因为 Java 是单继承)。

  • java.util.Observer 是一个接口。

  • Observable 的 notifyObservers() 方法可以传递一个参数 (arg),观察者的 update(Observable o, Object arg) 方法会接收到主题对象和这个参数。

  • 它们被标记为 @Deprecated 是因为其设计上的一些限制(例如 Observable 是类,以及 setChanged() 方法需要显式调用才能使 notifyObservers() 生效)。现代 Java 中更推荐使用更灵活的事件监听机制或反应式编程库 (如 RxJava, Project Reactor)。

总结:

观察者模式是一种实现对象间解耦的有效方式,使得当一个对象的状态改变时,能够自动通知依赖于它的其他对象。它在许多应用场景中都非常有用,尤其是在需要事件驱动或数据绑定机制时。理解其核心思想和优缺点,可以帮助你更好地设计和构建灵活、可维护的系统。

相关推荐
Scott9999HH3 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔3 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社4 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海4 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖5 小时前
JDK 26 新特性详解
java·开发语言
马优晨5 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区7 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大8 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai8 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说8 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json