观察者模式(发布-订阅模式)

本文详细介绍了观察者模式的概念、应用场景及其实现原理。通过具体的Java代码示例,展示了如何使用观察者模式来实现对象间的解耦,并保持一致性。讨论了模式的优点、缺点以及Java平台对观察者模式的支持。

观察者模式(发布-订阅模式)

一、概述

  观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
  观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
动机:若将一个系统分割成一系列相互协作的类,需要维护相关对象间的一致性。我们不希望为了维护对象间的一致性而使各类紧密耦合。这样会给维护、扩展和重用都带来了不变。而观察者模式的关键对象是主题Subject和观察者Observer,一个Subject可以有任何数目的依赖它的Observer,一旦Subject的状态发生变化,所有的Observer都可以得到通知。具体观察者是谁,subject不需知道。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。而且它不知道具体有多少对象有待改变时,应该考虑使用它。
如何解决:使用面向对象技术,可以将这种依赖关系弱化。
关键代码:在抽象类里有一个 ArrayList 存放注册的观察者。

优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
2、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

使用场景

一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
一个对象必须通知其他对象,而并不知道这些对象是谁。
需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
总的来说,观察者模式所做的工作其实就是解除耦合,让耦合的双方都依赖于抽象,而不是依赖具体,从而使得各自的变化不会影响另一方的变化,这正是依赖倒转原则的体现。

二、实现

例子uml图
观察者模式
观察者模式中的角色:
抽象主题(Subject)角色:抽象主题角色提供维护一个观察者对象聚集的操作方法,对聚集的增加、删除等。
具体主题(ConcreteSubject)角色:将有关状态存入具体的观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色负责实现抽象主题中聚集的管理方法。
抽象观察者(Observer)角色:为具体观察者提供一个更新接口。
具体观察者(ConcreteObserver)角色:存储与主题相关的自洽状态,实现抽象观察者提供的更新接口。
代码实现:代码注解有说明

/**
 * 主题或抽象通知者,一般用一个抽象类或一个接口实现。
 * 它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何
 * 数量的观察者。抽象主题提供一个接口可以增加和删除观察者对象。
 */

import java.util.ArrayList;
import java.util.List;

public abstract class Subject {
    private List<Observer> observers = new ArrayList<>();
    //增加观察者
    public void attach(Observer observer) {
        observers.add(observer);
    }
    //移除观察者
    public void detach(Observer observer) {
        observers.remove(observer);
    }
    //通知
    public void notifyObserver(String state) {
        for (Observer observer : observers) {
            observer.update(state);
        }
    }
    public abstract void changeState(String state);
}
/**
 * 具体主题或具体通知者,将有关状态存入具体观察者对象,在具体主题
 * 的内部状态改变时,给所有注册过的观察者发送通知。
 */
public class ConcreteSubject extends Subject {
    private String state;

    public String getState() {
        return state;
    }
    public void changeState(String state) {
        this.state = state;
        System.out.println("主题状态:"+state);
        this.notifyObserver(state);
    }
}
/**
 * 抽象观察者,为所有的具体观察者定义一个接口
 * 在得到主题的通知时更新自己。
 * 抽象观察者一般用一个抽象类或一个接口实现。
 */
public interface Observer {
    /**
     * 更新方法
     * @param state 更新状态
     */
    void update(String state);
    //还可以传入主题对象,方便获取相应的主题对象的状态
    //void update(Subject);
}
/**
 * 具体观察者,实现抽象观察者角色所要求的更新接口,
 * 以便使自身的状态与主题的协调。具体观察者也可以
 * 保存一个指向具体主题的引用。
 */
public class ConcreteObserver implements Observer {
    private String observerState;//观察者状态
    /*
     *更新观察者的状态,使其跟目标的状态一致
     */
    @Override
    public void update(String state) {
        this.observerState=state;
        System.out.println("观察者状态:"+state);
    }
}
public class ConcreteObserver2 implements Observer {
    private String observerState;//观察者状态
    /*
     *更新观察者的状态,使其跟目标的状态一致
     */
    @Override
    public void update(String state) {
        this.observerState=state;
        System.out.println("观察者2状态:"+state);
    }
}
    public static void main(String[] args) {
        //创建主题
        Subject subject = new ConcreteSubject();
        //创建观察者对象
        Observer observer = new ConcreteObserver();
        Observer observer2 = new ConcreteObserver2();
        //注册观察者
        subject.attach(observer);
        subject.attach(observer2);
        //改变主题状态
        subject.changeState("重启");
    }

三、JAVA 中对观察者模式的支持

  在JAVA语言的java.util库里面,提供了一个Observable类以及一个Observer接口,构成JAVA语言对观察者模式的支持。
1、public interface Observer接口
当一个类想要被告知可观察对象的变化时,它可以实现Observer接口。
源码:

public interface Observer {
    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     * <code>notifyObservers</code> method to have all the object's
     * observers notified of the change.
     *
     * @param   o     the observable object.
     * @param   arg   an argument passed to the <code>notifyObservers</code>
     *                 method.
     */
    void update(Observable o, Object arg);
}

该接口有个update抽象方法。每当观察对象发生变化时,被观察者对象的notifyObservers()方法就会调用这一方法。
2、java.util.Observable类
java.util.Observable提供公开的方法支持观察者对象。其中重要的方法:
setChanged():被调用之后会设置一个内部标记变量,代表被观察者对象的状态发生了变化。
notifyObservers():被调用时,会调用所有登记过的观察者对象的update()方法,使这些观察者对象可以更新自己。
addObserver(Observer o):将观察者添加到此对象的观察者集中,前提是它与集合中已有的某个观察者不同。 未指定将通知发送给多个观察者的顺序。

举例
在教室上计算机课,张三在打游戏,李四在睡觉,他们叫坐在门口的那位同学小刚当老师来的时候通知他们。
例子简单实现;
被观察者(主题)

public class XiaoGang extends Observable {
    private String state;
    public XiaoGang(List<Observer> list) {
        for (Observer o : list) {
            addObserver(o);
        }
    }
    public void changeState(String state) {
        this.state =  state;
        setChanged();
        notifyObservers(this.state);
    }
}

观察者:

public class Lisi implements Observer {
    private String state;
    @Override
    public void update(Observable o, Object arg) {
        this.state = (String)arg;
        System.out.println("李四,"+state+",别睡觉了");
    }
}
public class Zhangsan implements Observer {
    private String state;
    @Override
    public void update(Observable o, Object arg) {
        this.state = (String)arg;
        System.out.println("张三,"+state+",别打游戏了");
    }

}

客户端测试

    public static void main(String[] args) {
        Lisi ls = new Lisi();
        Zhangsan zs = new Zhangsan();
        List<Observer> list = new ArrayList<>();
        list.add(ls);
        list.add(zs);
        XiaoGang xg = new XiaoGang(list);
        xg.changeState("老师回来了");
    }

结果:
张三,老师回来了,别打游戏了
李四,老师回来了,别睡觉了

<think>我们正在讨论观察者模式发布-订阅模式的区别。根据提供的引用,我们可以总结如下: 引用[1]指出,发布-订阅模式观察者模式多了一个调度中心(或事件通道),这使得发布者和订阅者之间没有直接联系,而是通过调度中心来管理。这种设计提高了灵活性。 引用[2]进一步说明,发布-订阅模式是基于一个事件通道的,订阅者通过自定义事件订阅主题,发布者则通过发布主题事件来通知订阅者。 引用[3]则强调观察者模式中,被观察对象(Subject)和观察者(Observer)是直接关联的,Subject需要维护一个观察者列表,并提供添加、删除和通知观察者的方法。 因此,我们可以将两者的区别归纳如下: 1. **耦合度**: - 观察者模式:Subject和Observer直接交互,Subject知道Observer的存在(通过维护观察者列表)。 - 发布-订阅模式发布者和订阅者不直接交互,而是通过一个中间件(事件通道或调度中心)进行通信,实现了完全解耦。 2. **通信方式**: - 观察者模式:Subject在状态变化时,直接调用Observer的更新方法(如`update()`)。 - 发布-订阅模式发布者将事件发布到调度中心,调度中心根据事件类型将事件分发给相应的订阅者。 3. **灵活性**: - 发布-订阅模式由于中间件的存在,可以支持更复杂的场景,例如过滤事件、多对多通信、异步处理等。而观察者模式通常是同步的,并且是一对多的关系。 4. **实现复杂度**: - 观察者模式实现较为简单,通常只需要在Subject中维护一个观察者列表。 - 发布-订阅模式需要实现一个调度中心,相对复杂。 下面用代码示例说明: **观察者模式示例**: ```dart // 被观察者(Subject) class Subject { List<Observer> _observers = []; void addObserver(Observer observer) { _observers.add(observer); } void removeObserver(Observer observer) { _observers.remove(observer); } void notifyObservers() { for (var observer in _observers) { observer.update(); } } } // 观察者(Observer) abstract class Observer { void update(); } // 具体观察者 class ConcreteObserver implements Observer { @override void update() { print('Observer notified!'); } } ``` **发布-订阅模式示例**: ```dart // 事件调度中心 class EventBus { final Map<String, List<Function>> _subscriptions = {}; void subscribe(String eventType, Function callback) { _subscriptions.putIfAbsent(eventType, () => []).add(callback); } void publish(String eventType) { if (_subscriptions.containsKey(eventType)) { for (var callback in _subscriptions[eventType]!) { callback(); } } } } // 发布者 class Publisher { final EventBus eventBus; Publisher(this.eventBus); void publishEvent(String eventType) { eventBus.publish(eventType); } } // 订阅者 class Subscriber { final EventBus eventBus; Subscriber(this.eventBus); void subscribe(String eventType, Function callback) { eventBus.subscribe(eventType, callback); } } ``` 在发布-订阅模式中,发布者和订阅者通过事件总线(EventBus)进行通信,双方互不认识。 总结: | 特性 | 观察者模式 | 发布-订阅模式 | |--------------|------------------------------|----------------------------------| | **耦合度** | 紧耦合(Subject直接引用Observer) | 松耦合(通过中间件) | | **通信方式** | 直接调用 | 通过事件通道 | | **关系** | 通常是一对多 | 多对多(通过不同事件类型) | | **灵活性** | 较低 | 高(支持事件过滤、异步等) | | **复杂度** | 简单 | 相对复杂 | 在Flutter中,观察者模式(如`ChangeNotifier`)是直接通知监听器的,而发布-订阅模式可以使用事件总线(如`event_bus`库)或`Stream`来实现。例如,`Stream`和`StreamController`就构成了发布-订阅模式,其中`StreamController`作为发布者,`StreamSubscription`作为订阅者,而`Stream`本身则充当了事件通道的角色[^1][^2]。 §§相关问题§§ 1. 在Flutter中如何实现一个事件总线(Event Bus)? 2. 观察者模式发布-订阅模式各自的优缺点是什么? 3. 如何选择使用观察者模式还是发布-订阅模式?</think>### 观察者模式 vs 发布-订阅模式的区别 #### 1. **核心架构差异** | 特性 | 观察者模式 | 发布-订阅模式 | |--------------|------------------------------|----------------------------------| | **中间层** | 无直接中介 | 存在事件通道/调度中心[^1] | | **耦合度** | 观察者与被观察者直接绑定 | 发布者与订阅者完全解耦[^2] | | **通信方式** | 被观察者主动通知观察者 | 通过调度中心路由事件[^3] | #### 2. **实现机制对比 **观察者模式**(直接通知): ```dart // 被观察者 class Subject { final List<Observer> _observers = []; void addObserver(Observer o) => _observers.add(o); void notify() => _observers.forEach((o) => o.update()); } // 观察者 abstract class Observer { void update(); } ``` **发布-订阅模式**(通过事件总线): ```dart // 事件总线(调度中心) class EventBus { final Map<String, List<Function>> _subscriptions = {}; void subscribe(String event, Function callback) { _subscriptions.putIfAbsent(event, () => []).add(callback); } void publish(String event) { _subscriptions[event]?.forEach((fn) => fn()); } } // 使用示例 final bus = EventBus(); bus.subscribe('user_login', () => print("Login handled")); bus.publish('user_login'); // 通过调度中心触发 ``` #### 3. **关键区别点** 1. **关系建立方式** - 观察者模式:被观察者**主动管理**观察者列表(添加/删除)[^3] - 发布-订阅订阅者**向调度中心注册**,发布者**不知订阅者存在**[^1] 2. **事件处理能力** - 观察者模式:通常处理**单一主题**变更 - 发布-订阅:支持**多主题事件过滤**(如`eventBus.subscribe('topic.*')`)[^2] 3. **系统复杂度** - 观察者模式:适合**简单依赖关系**(如按钮点击监听) - 发布-订阅:解决**跨组件/跨系统通信**(如微服务事件驱动)[^1] #### 4. **应用场景差异** - **观察者模式适用**: ✅ 局部状态更新(如Flutter的`ValueNotifier`) ✅ 父子组件直接通信 ✅ 简单的一对多通知场景 - **发布-订阅适用**: ✅ 微服务间异步通信(如Kafka) ✅ 插件系统事件调度 ✅ 需要事件过滤的复杂系统(`firebase_messaging`的topic订阅) > 两者本质都是**事件驱动范式**,但发布-订阅通过调度中心实现更彻底的解耦,为系统提供更强的扩展性和灵活性[^1][^2]。在Flutter中,`Stream`+`StreamController`可视为轻量级发布-订阅实现。 --- ### 数学关系表达 两者的事件传递可用函数描述: - **观察者模式**:直接映射 $$f: Subject \times Observer \rightarrow Notification$$ - **发布-订阅**:通过调度中心$S$中转 $$f: Publisher \times Event \xrightarrow{S} Subscriber$$ ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值