设计模式之观察者模式

设计模式之观察者模式

  • 观察者模式是什么?解决了什么问题?
  • 什么情况下适用观察者模式?
  • 怎么实现观察者模式?
  • 现在的OOM系统里面,有哪些地方用到了观察者模式呢?

一、模式介绍

1.1 意图

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

1.2 动机

将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,因为这样降低了它们的可重用性。

例如, 许多图形用户界面工具箱将用户应用的界面表示与底下的应用数据分离。定义应用数据的类和负责界面表示的类可以各自独立地复用。 当然它们也可一起工作。一个表格对象和一个柱状图对象可使用不同的表示形式描述同一个应用数据对象的信息。表格对象和柱状图对象互相并不知道对方的存在,这样使你可以根据需要单独复用表格或柱状图。但在这里是它们表现的似乎互相知道。当用户改变表格中的信息时 , 柱状图能立即反映这一变化, 反过来也是如此。

这一行为意味着表格对象和棒状图对象都依赖于数据对象 , 因此数据对象的任何状态改变都应立即通知它们。同时也没有理由将依赖于该数据对象的对象的数目限定为两个, 对相同的数据可以有任意数目的不同用户界面。
在这里插入图片描述

观察者模式描述了如何建立这种关系。这一模式中的关键对象是目标( subject )和观察者 ( observer )。一个目标可以有任意数目的依赖它的观察者。一旦目标的状态发生改变 , 所有的观察者都得到通知。作为对这个通知的响应,每个观察者都将查询目标以使其状态与目标的状态同步。

这种交互也称为发布-订阅(publish - subscribe)。目标是通知的发布者。它发出通知时并不需知道谁是它的观察者。可以有任意数目的观察者订阅并接收通知。

1.3 适用性

在以下任一情况下可以使用观察者模式 :

  • 当一个抽象模型有两个方面 , 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  • 当对一个对象的改变需要同时改变其它对象 , 而不知道具体有多少对象有待改变。
  • 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之 , 你不希望这些对象是紧密耦合的。
1.4 结构

在这里插入图片描述

1.5 参与者
  • 目标(Subject):目标知道它的观察者。可以有任意多个观察者观察同一个目标。 提供注册和删除观察者对象的接口。
  • 观察者(Observer):为那些在目标发生改变时需获得通知的对象定义一个更新接口。
  • 具体目标(ContreteSubject):将有关状态存入各 ConcreteObserver 对象。 当它的状态发生改变时, 向它的各个观察者发出通知。
  • 具体观察者(ConcreteObserver ): 维护一个指向 ContreteSubject 对象的引用。 存储有关状态,这些状态应与目标的状态保持一致。 实现 Observer 的更新接口以使自身状态与目标的状态保持一致。
1.6 协作
  • 当 ContreteSubject 发生任何可能导致其观察者与其本身状态不一致的改变时,它将通知它的各个观察者。
  • 在得到一个具体目标的改变通知后 , ConcreteObserver 对象可向目标对象查询信息。ConcreteObserver 使用这些信息以使它的状态与目标对象的状态一致。

注意发出改变请求的 Observer 对象并不立即更新,而是将其推迟到它从目标得到一个通知之后。Notify 不总是由目标对象调用。它也可被一个观察者或其它对象调用。实现一节将讨论 一些常用的变化。

1.7 效果

Observer 模式允许你独立的改变目标和观察者。你可以单独复用目标对象而无需同时复用其观察者, 反之亦然。它也使你可以在不改动目标和其他的观察者的前提下增加观察者。

下面是观察者模式其它一些优缺点:

  • 目标和观察者间的抽象耦合 :一个目标所知道的仅仅是它有一系列观察者 , 每个都符合抽象的 Observer 类的简单接口。目标不知道任何一个观察者属于哪一个具体的类。这样目标和观察者之间的耦合是抽象的和最小的。

    因为目标和观察者不是紧密耦合的 , 它们可以属于一个系统中的不同抽象层次。一个处于较低层次的目标对象可与一个处于较高层次的观察者通信并通知它 , 这样就保持了系统层次的完整。如果目标和观察者混在一块 , 那么得到的对象要么横贯两个层次 (违反了层次性), 要么必须放在这两层的某一层中(这可能会损害层次抽象)。

  • 支持广播通信:不像通常的请求, 目标发送的通知不需指定它的接收者。通知被自动广播给所有已向该目标对象登记的有关对象。目标对象并不关心到底有多少对象对自己感兴趣 ; 它唯一的责任就是通知它的各观察者。这给了你在任何时刻增加和删除观察者的自由。处理还是忽略一个通知取决于观察者。

  • 意外的更新 :因为一个观察者并不知道其它观察者的存在 , 它可能对改变目标的最终代价一无所知。在目标上一个看似无害的的操作可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新。此外 , 如果依赖准则的定义或维护不当,常常会引起错误的更新 , 这种错误通常很难捕捉。

简单的更新协议不提供具体细节说明目标中什么被改变了 , 这就使得上述问题更加严重。 如果没有其他协议帮助观察者发现什么发生了改变,它们可能会被迫尽力减少改变。

二、实现

2.1 实现的相关问题
  1. 创建目标到其观察者之间的映射:一个目标对象跟踪它应通知的观察者的最简单的方法是显式地在目标中保存对它们的引用。然而 , 当目标很多而观察者较少时 , 这样存储可能代价太高。一个解决办法是用时间换空间 , 用一个关联查找机制(例如一个hash表)来维护目标到观察者的映射。这样一个没有观察者的目标就不产生存储开销。但另一方面 , 这一方法增加了访问观察者的开销。

  2. 观察多个目标 :在某些情况下, 一个观察者依赖于多个目标可能是有意义的。例如 , 一个表格对象可能依赖于多个数据源。在这种情况下 , 必须扩展 Update 接口以使观察者知道是哪一个目标送来的通知。目标对象可以简单地将自己作为 Update 操作的一个参数, 让观察者知道应去检查哪一个目标。

  3. 谁触发更新:目标和它的观察者依赖于通知机制来保持一致。但到底哪一个对象调用 Notify 来触发更新?

    此时有两个选择:

    • 由目标对象的状态设定操作在改变目标对象的状态后自动调用 Notify 。这种方法的优点是客户不需要记住要在目标对象上调用 Notify ,缺点是多个连续的操作会产生多次连续的更新, 可能效率较低。
    • 让客户负责在适当的时候调用 Notify 。这样做的优点是客户可以在一系列的状态改变完成后再一次性地触发更新 ,避免了不必要的中间更新。缺点是给客户增加了触发更新的责任。由于客户可能会忘记调用Notify ,这种方式较易出错。
  4. 对已删除目标的悬挂引用 :删除一个目标时应注意不要在其观察者中遗留对该目标的悬挂引用。一种避免悬挂引用的方法是 , 当一个目标被删除时,让它通知它的观察者将对该目标的引用复位。一般来说, 不能简单地删除观察者, 因为其他的对象可能会引用它们, 或者也可能它们还在观察其他的目标。

  5. 在发出通知前确保目标的状态自身是一致的 : 在发出通知前确保状态自身一致这一点很重要, 因为观察者在更新其状态的过程中需要查询目标的当前状态。 一种常见的错误是,先发出通知,然后才修改状态。这样观察者读到的状态就可能不一致。

    你可以用抽象的 Subject 类中的模板方法发送通知来避免这种错误。定义那些子类可以重定义的原语操作 , 并将 Notify 作为模板方法中的最后一个操作 , 这样当子类重定义了 Subject 的操作时,还可以保证该对象的状态是自身一致的。

  6. 避免特定于观察者的更新协议—推/拉模型 :观察者模式的实现经常需要让目标广播关于其改变的其他一些信息。目标将这些信息作为 Update 操作一个参数传递出去。这些信息的量可能很小,也可能很大。

    一个极端情况是,目标向观察者发送关于改变的详细信息 , 而不管它们需要与否。我们称之为推模型(push model)。另一个极端是拉模型(pull model); 目标除最小通知外什么也不送出, 而在此之后由观察者显式地向目标询问细节。

    拉模型强调的是目标不知道它的观察者 , 而推模型假定目标知道一些观察者的需要的信息。推模型可能使得观察者相对难以复用,因为目标对观察者的假定可能并不总是正确的。 另一方面。拉模型可能效率较差 , 因为观察者对象需在没有目标对象帮助的情况下确定什么改变了。

  7. 显式地指定感兴趣的改变 :你可以扩展目标的注册接口,让各观察者注册为仅对特定事件感兴趣,以提高更新的效率。当一个事件发生时 , 目标仅通知那些已注册为对该事件感兴趣的观察者。支持这种做法一种途径是,对使用目标对象的方面(a s p e c t s)的概念。

  8. 封装复杂的更新语义 : 当目标和观察者间的依赖关系特别复杂时 , 可能需要一个维护这些关系的对象。我们称这样的对象为更改管理器(ChangeManager)。它的目的是尽量减少观察者反映其目标的状态变化所需的工作量。例如 , 如果一个操作涉及到对几个相互依赖的目标进行改动, 就必须保证仅在所有的目标都已更改完毕后,才一次性地通知它们的观察者 ,而不是每个目标都通知观察者。

    ChangeManager 有三个责任:

    • 它将一个目标映射到它的观察者并提供一个接口来维护这个映射。这就不需要由目标来维护对其观察者的引用, 反之亦然。
    • 它定义一个特定的更新策略。
    • 根据一个目标的请求, 它更新所有依赖于这个目标的观察者。
2.2 案例

需求来了,现在要你做一个气象观测站,有一个 WeatherData 类提供给你,这个类提供了对目前天气情况的追踪(气温、湿度、气压),现在请你建立一个应用,可以展示三种布告板,分别展示目前的情况、气象统计和简单的预报。这三种布告板必须实时更新。

先来画一波类图:
在这里插入图片描述

我们先从接口开始:

// 主题接口,提供注册、删除、通知观察者的接口方法
public interface Subject {

    void registerObserver(Observer o);

    void removeObserver(Observer o);

    void notifyObserver();
}

// 观察者接口,定义统一的更新方法
public interface Observer {
    void update(float temperature, float humidity, float pressure);
}

// 布告板展示接口
public interface DisplayElement {
    void display();
}

在 WeatherData 中实现主题接口

public class WeatherData implements Subject {

    // 建立List 用来保持对观察者的引用,便于通知观察者
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        this.observers = new ArrayList<>();	// 构造器中建立
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        int i = observers.indexOf(o);
        if(i>=0){
            observers.remove(i);
        }
    }

    // 挨个通知所有的观察者
    @Override
    public void notifyObserver() {
        for (int i = 0; i < observers.size(); i++) {
            Observer observer = observers.get(i);
            observer.update(temperature,humidity,pressure);
        }
    }

    // 从气象站得到最新观测值时,通知它的观察者
    public void measurementsChanged(){
        notifyObserver();
    }

    public void setMeasurements(float temperature, float humidity, float pressure){
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

现在来写布告板 CurrentConditionDisplay 。(还有两个布告板)

// 当前状况布告板 实现了 Observer 接口,所以可以从 WeatherData 对象中获取改变
// 实现了 DisplayElement 接口,所有的布告板都必须实现这个接口
public class CurrentConditionDisplay implements Observer, DisplayElement {

    private float temperature;
    private float humidity;
    private Subject weatherData;

    // 构造器需要 weatherData 对象,也就是主题作为注册之用。这里为什么要保持对 weatherData 的引用呢?
    // 因为以后可能还想取消注册,如果有weatherData引用,会比较方便
    public CurrentConditionDisplay(Subject weatherData){
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("Current conditions: "+ temperature + " F degrees and " + humidity + " % humidity");
    }

    // 当 update 方法被调用时,我们把 temperature 和 humidity 保存起来,并调用 display 方法
    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }
}

测试一下:

public class Main {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);

        weatherData.setMeasurements(80,65,30.4f);
        weatherData.setMeasurements(78,37,29.4f);
        weatherData.setMeasurements(82,80,33.4f);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
观察者模式是一种常用的设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象状态发生变化时,它的所有观察者都会收到通知并更新自己的状态。 在C++中,观察者模式通常由一个抽象主题类和多个具体观察者类组成。抽象主题类中定义了添加、删除和通知观察者的接口,具体观察者类实现了更新自身状态的方法。 以下是一个简单的观察者模式示例: ```c++ #include <iostream> #include <vector> class Observer { public: virtual void update() = 0; }; class Subject { public: void attach(Observer* observer) { observers.push_back(observer); } void detach(Observer* observer) { for (auto it = observers.begin(); it != observers.end(); ++it) { if (*it == observer) { observers.erase(it); break; } } } void notify() { for (auto observer : observers) { observer->update(); } } private: std::vector<Observer*> observers; }; class ConcreteObserver1 : public Observer { public: void update() override { std::cout << "ConcreteObserver1 updated" << std::endl; } }; class ConcreteObserver2 : public Observer { public: void update() override { std::cout << "ConcreteObserver2 updated" << std::endl; } }; int main() { Subject subject; ConcreteObserver1 observer1; ConcreteObserver2 observer2; subject.attach(&observer1); subject.attach(&observer2); subject.notify(); subject.detach(&observer1); subject.notify(); return 0; } ``` 在上面的示例中,Subject类是抽象主题类,Observer类是抽象观察者类,ConcreteObserver1和ConcreteObserver2是具体观察者类。当Subject对象状态发生变化时,它会通知所有观察者更新自己的状态。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值