设计模式之观察者模式

定义

当一个对象的状态发生变化时,能够自动通知其他关联对象,自动刷新对象状态。观察者模式提供给关联对象一种同步通信的手段,使某个对象与依赖它的其他对象之间保持状态同步

发生改变的对象称之为观察目标,而被通知的对象称之为观察者。一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,所以可以根据需要增加和删除观察者,使得系统更易于扩展。

观察者模式又称为发布-订阅模式。

模式包含四个角色

  • Subject抽象被观察者角色:也就是一个抽象主题,它把所有对观察者对象的引用保存在一个集合中,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
  • Observer抽象观察者角色:为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
  • ConcreteSubject具体被观察者角色:也就是一个具体的主题,在集体主题的内部状态改变时,所有登记过的观察者发出通知(Callback)。
  • ConcreteObserver具体观察者角色:实现抽象观察者角色所需要的更新接口,一边使本身的状态与制图的状态相协调。

 

实例:

气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)。
需要设计开放型API,便于其他第三方也能接入气象站获取数据。
 提供温度、气压和湿度的接口
测量数据更新时,要能实时的通知给第三方

类图说明

Subject接口提供了注册、移除、通知Observer 的方法

 WeatherData实现Subject 

* 1. 包含最新的天气情况信息
* 2. 含有观察者集合,使用ArrayList 管理
* 3. 当数据有更新时,就主动的调用ArrayList, 通知所有的(接入方)就看到最新的信息

Observer有update方法更新天气信息

 代码实现

package com.dongguo.observer;

/**
 * @author Dongguo
 * @date 2021/8/23 0023-9:07
 * @description: 观察者接口,有观察者来实现
 */
public interface Observer {
    public void update(float temperature, float pressure, float humidity);
}

package com.dongguo.observer;

/**
 * @author Dongguo
 * @date 2021/8/23 0023-9:05
 * @description: 接口, 让WeatherData 来实现
 */
public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}
package com.dongguo.observer;

/**
 * @author Dongguo
 * @date 2021/8/23 0023-9:08
 * @description:
 */
public class BaiduSite implements Observer {
    // 温度,气压,湿度
    private float temperature;
    private float pressure;
    private float humidity;

    // 更新天气情况,是由WeatherData 来调用,我使用推送模式
    public void update(float temperature, float pressure, float humidity) {
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        display();
    }

    // 显示
    public void display() {
        System.out.println("===百度网站====");
        System.out.println("***百度网站气温: " + temperature + "***");
        System.out.println("***百度网站气压: " + pressure + "***");
        System.out.println("***百度网站湿度: " + humidity + "***");
    }
}

 

package com.dongguo.observer;

/**
 * @author Dongguo
 * @date 2021/8/23 0023-9:09
 * @description:
 */
public class CurrentConditions implements Observer {
    // 温度,气压,湿度
    private float temperature;
    private float pressure;
    private float humidity;
    // 更新天气情况,是由WeatherData 来调用,我使用推送模式
    public void update(float temperature, float pressure, float humidity) {
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        display();
    }
    // 显示
    public void display() {
        System.out.println("***Today mTemperature: " + temperature + "***");
        System.out.println("***Today mPressure: " + pressure + "***");
        System.out.println("***Today mHumidity: " + humidity + "***");
    }
}

package com.dongguo.observer;

import java.util.ArrayList;

/**
 * @author Dongguo
 * @date 2021/8/23 0023-9:10
 * @description:
 */
public class WeatherData implements Subject {
    private float temperatrue;
    private float pressure;
    private float humidity;
    //观察者集合
    private ArrayList<Observer> observers;

    //加入新的第三方
    public WeatherData() {
        observers = new ArrayList<Observer>();
    }

    public float getTemperature() {
        return temperatrue;
    }

    public float getPressure() {
        return pressure;
    }

    public float getHumidity() {
        return humidity;
    }

    public void dataChange() {
//调用接入方的update
        notifyObservers();
    }

    //当数据有更新时,就调用setData
    public void setData(float temperature, float pressure, float humidity) {
        this.temperatrue = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
//调用dataChange, 将最新的信息推送给接入方currentConditions
        dataChange();
    }

    //注册一个观察者
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    //移除一个观察者
    @Override
    public void removeObserver(Observer o) {
        if (observers.contains(o)) {
            observers.remove(o);
        }
    }

    //遍历所有的观察者,并通知
    @Override
    public void notifyObservers() {
        for (int i = 0; i < observers.size(); i++) {
            observers.get(i).update(this.temperatrue, this.pressure, this.humidity);
        }
    }
}
package com.dongguo.observer;

/**
 * @author Dongguo
 * @date 2021/8/23 0023-9:12
 * @description:
 */
public class Client {
    public static void main(String[] args) {
//创建一个WeatherData
        WeatherData weatherData = new WeatherData();
//创建观察者
        CurrentConditions currentConditions = new CurrentConditions();
        BaiduSite baiduSite = new BaiduSite();
//注册到weatherData
        weatherData.registerObserver(currentConditions);
        weatherData.registerObserver(baiduSite);
//测试
        System.out.println("通知各个注册的观察者, 看看信息");
        weatherData.setData(10f, 100f, 30.3f);
        weatherData.removeObserver(currentConditions);
//测试
        System.out.println();
        System.out.println("通知各个注册的观察者, 看看信息");
        weatherData.setData(10f, 100f, 30.3f);
    }
}
运行结果:
通知各个注册的观察者, 看看信息
***Today mTemperature: 10.0***
***Today mPressure: 100.0***
***Today mHumidity: 30.3***
===百度网站====
***百度网站气温: 10.0***
***百度网站气压: 100.0***
***百度网站湿度: 30.3***

通知各个注册的观察者, 看看信息
===百度网站====
***百度网站气温: 10.0***
***百度网站气压: 100.0***
***百度网站湿度: 30.3***

观察者模式在Jdk 应用的源码分析

Jdk 的Observable 类就使用了观察者模式

package java.util;

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();
    }
}

Observable 的作用和地位等价于我们前面讲过Subject
 Observable 是类,不是接口,类中已经实现了核心的方法,即管理Observer 的方法add.. delete .. notify...
Observer 的作用和地位等价于我们前面讲过的Observer, 有update

package java.util;

public interface Observer {

    void update(Observable o, Object arg);
}


Observable 和Observer 的使用方法和前面讲过的一样,只是Observable 是类,通过继承来实现观察者模式

优点:

1、当两个对象之间送耦合,他们依然可以交互,但是不太清楚彼此的细节。观察者模式提供了一种对象设计,让主题和观察者之间送耦合。主题所知道只是一个具体的观察者列表,每一个具体观察者都符合一个抽象观察者的接口。主题并不认识任何一个具体的观察者,它只知道他们都有一个共同的接口。

2、观察者模式支持“广播通信”。主题会向所有的观察者发出通知。

3、观察者模式符合“开闭原则”的要求。

缺点:

1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

2、 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进 行循环调用,可能导致系统崩溃。

3、 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

观察者模式的适用场所

​ 1、一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。

2、一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。

3、一个对象必须通知其他对象,而并不知道这些对象是谁。需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

总结

1、观察者模式定义了对象之间的一对多关系。多个观察者监听同一个被观察者,当该被观察者的状态发生改变时,会通知所有的观察者。

2、观察者模式中包含四个角色。主题,它指被观察的对象。具体主题是主题子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;观察者,将对观察主题的改变做出反应;具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致。

3、主题用一个共同的接口来更新观察者。

4、观察者与被观察者之间用松耦合方式结合。

5、有多个观察者时,不可以依赖特定的通知次序。

6、使用观察者模式,可以从被观察者处推或者拉数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失业找工作中

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值