设计模式--观察者模式

1、角色说明

观察者模式中,主要有两类角色:主题(subject)和观察者(observer)。

  • 主题:采集数据,将更新消息推送给订阅该主题的所有观察者;
  • 观察者: 订阅了主题之后,可以从主题中接收通知;

此模式结构类图如下:
在这里插入图片描述

2、实例问题

java中内置有观察者模式,但是为了更好地理解此模式,我们以《Head First 设计模式》一书中的例子为切入点,自己搭建一套关于天气的观察者模式Demo。

问题描述如下:

  • WeatherData获得最新的测量数据(温度(temprature)、湿度(humidity)、气压(pressure);
  • CurrentConditionDisplayer实时更新显示当前最新天气数据;

为解决上述问题,我们建立如下所示结构:
在这里插入图片描述

其中:

  • ISubject:主题接口,定义主题所需基本行为–观察者订阅入口(registerObserver)、观察者解除订阅入口(removeObserver)、通知所有已订阅观察者方法(notifyObservers);
  • IObserver:观察者接口,定义所有观察者所需基本行为–接收主题推送的消息,更新自己的数据内容(update);
  • IDisplayElement:展示接口,和IObserver一起规定观察者的行为准则–显示数据(display)

3、问题实践

对应提出的问题,实现上述接口的类内容如下:

天气数据主题类(WeatherData)

package design.practice.weather.data.displayer.subject;

import design.practice.weather.data.displayer.observer.IObserver;
import java.util.ArrayList;
import java.util.List;

/**
 * @author yanzy
 * @date 2018/11/5 上午9:29
 * @description 主题接口的一个实现类--天气数据接收器
 */
public class WeatherData implements ISubject {
    private List<IObserver> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        this.observers = new ArrayList<IObserver>();
    }

    @Override
    public void registerObserver(IObserver observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(IObserver observer) {
        if (observers.indexOf(observer) >= 0) {
            this.observers.remove(observer);
        }
    }

    @Override
    public void notifyObservers() {
        observers.forEach(o -> {
            o.update(this.temperature, this.humidity, this.pressure);
        });
    }

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

此为一主题:

  • 所有订阅此主题的观察这都存储在observers这个List当中,注册和取消订阅行为都以此为基础进行;
  • 当内容更新时,遍历此主题实力中的observers,逐一调用其update方法,完成消息推送。

当前天气条件展示面板(CurrentConditionDisplay)

package design.practice.weather.data.displayer.observer;

import design.practice.weather.data.displayer.subject.ISubject;

/**
 * @author yanzy
 * @date 2018/11/6 上午9:36
 * @description 展示面板--气温和湿度
 */
public class CurrentConditionDisplay implements IObserver, IDisplayElement {
    private ISubject subject;
    private Float temperature;
    private Float humidity;

    public CurrentConditionDisplay(ISubject subject) {
        this.subject = subject;
    }

    @Override
    public void display() {
        if (this.temperature == null && this.humidity == null) {
            System.out.println("no message from design.practice.weather.data.displayer.subject!\n");
        }
        System.out.println("current conditions: -" + this.temperature + "F- degrees and -" + this.humidity + "%- humidity");
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;

        this.display();
    }

    public void subscribe() {
        this.subject.registerObserver(this);
        System.out.println("success to subscribe " + subject);
    }

    public void unsubscribe() {
        this.subject.removeObserver(this);
        System.out.println("success to unsubscribe " + subject);
    }

    public float getTemperature() {
        return temperature;
    }

    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public void setHumidity(float humidity) {
        this.humidity = humidity;
    }
}

此为一观察者:

  • 实现IObserverIDisplayElement接口,其构造函数传入了一个ISubject接口,通过调用该接口实现类的registerObserver()removeObserver()方法来实现对主题的订阅和解除订阅操作;
  • 关键方法为update(),根据传入的值来更新当前观察者中的数据内容,并进行展示。

实例测试

package design.practice.weather.data.displayer;

import design.practice.weather.data.displayer.observer.CurrentConditionDisplay;
import design.practice.weather.data.displayer.subject.WeatherData;

/**
 * @author yanzy
 * @date 2018/11/6 上午9:47
 * @description
 */
public class Main {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        weatherData.setMeasurements(1F, 2F, 3F);
        System.out.println("no design.practice.weather.data.displayer.observer subscribe design.practice.weather.data.displayer.subject now!\n");

        CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
        currentConditionDisplay.subscribe();
        weatherData.setMeasurements(10F, 20F, 30F);

        currentConditionDisplay.unsubscribe();
        weatherData.notifyObservers();
    }
}

第一次无观察者订阅主题时,更新主题内容,无观察者接收到消息;后续CurrentConditionDisplay订阅主题,更新主题内容为10、20、30,CurrentConditionDisplay接受到通知,并显示当前最新天气数据内容;

执行结果如下:

no design.practice.weather.data.displayer.observer subscribe design.practice.weather.data.displayer.subject now!

success to subscribe design.practice.weather.data.displayer.subject.WeatherData@4f3f5b24
current conditions: -10.0F- degrees and -20.0%- humidity
success to unsubscribe design.practice.weather.data.displayer.subject.WeatherData@4f3f5b24

更多代码详情,请见:
https://github.com/yzy199391/HeadFirstDesignPatternPractice/tree/master/capter2observer

4、java内置观察者模式

通过上述实例,我们对于观察者模式已经有了一定的了解;其实,对于这种常用的设计模式,java中已经提供了现成的解决方案。

其大致思路与上述实例中无异:

  • 在java内置的模式中,上述实例中的主题实现类(WeatherData)通过继承Observeable类获取主题的基本行为实现。
  • 实现java.util.Observer接口,将对象声明为一个观察者,通过调用Observable的addObserver()方法订阅主题;通过调用deleteObserver()方法取消订阅。
  • 主题发送通知:先调用setChaged()方法改变chaged属性状态为true;调用两种notifyObservers()方法中的一种来逐一通知观察者–notifyObservers()/notifyObservers(Object arg);后一种方式可控制传送任何数据对象给每一个观察者。
  • 观察者接收通知:通过主题调用update(Observable o, Object arg)将数据推(push)给观察者,此时需要主题notify的方式为上述的第二种方式;否则,需要观察者自行从主题中拉取内容。

此处就不自行实现上述实践问题,引用《Head First 设计模式》一书中的样例来进行展示:
在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值