浅尝设计模式——如何使用观察者模式

本文深入解析观察者模式,探讨其核心概念与应用。通过气象站示例,展示如何利用观察者模式实现对象间一对多依赖,使主题状态改变时,所有观察者自动更新。讨论松耦合设计及Java内置观察者模式的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文为阅读《Head First 设计模式》一书的摘要总结

观察者模式

概念

定义

观察者 模式定义了对象之间的 一对多 依赖,这样一来,当一个对象改变状态时,它的所有依赖都会受到通知并自动更新。

类图

松耦合

观察者模式提供了一种对象设计,让主题和观察者之间松耦合。

主题唯一依赖的东西是一个实现Observer接口的 对象列表。当有新类型的观察者出现是,不需要改变主题代码,我们只需要新类型实现Observer接口,并注册为主题的观察者(加入对象列表)。当改变主题时(对自身状态操作的改变),只要它还实现Subject接口,观察者也不需要改变。所以它们之间是松耦合的。

示例

本示例的功能是气象站将温度、湿度和气压数据发送给不同的展示牌。

主题:

public interface Subject {
    void registryObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObserver();
}

public class WeatherData implements Subject{

    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;
    public WeatherData() {
        observers = new ArrayList<>();
    }

    @Override
    public void registryObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObserver() {
        for(Observer o : observers){
            o.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();
    }
}

观察者:

public interface Observer {
    void update(float temp, float humidity, float pressure);
}
public interface DisplayElement {
    void display();
}
public class CurrentConditionsDisplay implements Observer, DisplayElement{
    private float temperature;
    private float humidity;
    private float pressure;
    private Subject weatherData;

    public CurrentConditionsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registryObserver(this);
    }

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

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

其类图如下:

在这个示例中,主题直接将自己的状态(温度、湿度和气压)传入观察者,这种方式起始并不够好。若是以后主题有新的状态加入,那么主题和观察者代码都需要修改。

使用Java内置的观察者模式

java.util包下含有基本的Observable Observer 接口。这跟我们之前定义的Subject接口与Observer接口十分相似。但是使用Java内置的Observable类和Observer接口,我们可以使用 推(push)拉(pull) 的方式来传送数据。

Observable类的方法:

Observer接口定义的方法:

现在我们来重构上面的代码:

推数据

可观察者:

public class WeatherData extends Observable {

    private List<Observer> observers;
    private Data data;
    public WeatherData() {
        observers = new ArrayList<>();
        data = new Data();
    }
    public void measurementsChanged(){
        setChanged();
        notifyObservers(data);//将数据推送给观察者
    }
    public void setMeasurements(float temperature,float humidity,float pressure){
        data.setTemperature(temperature);
        data.setHumidity(humidity);
        data.setPressure(pressure);
        measurementsChanged();
    }
}

在调用notifyObservers方法之前,我们调用了setChanged方法,这是必要的,我们看一下源码中notifyObserverssetChaged方法的实现:

public class Observable {
 	private boolean changed = false;
    private Vector<Observer> obs;
	protected synchronized void setChanged() {
	    changed = true;
	}
	public void notifyObservers(Object arg) {
	    Object[] arrLocal;
	    synchronized (this) {
	        if (!changed)//只有changed为true时,才会通知观察者
	            return;
	        arrLocal = obs.toArray(); //obs是订阅了该主题的观察者列表
	        clearChanged();
	    }
	    for (int i = arrLocal.length-1; i>=0; i--)
	        ((Observer)arrLocal[i]).update(this, arg);
	}
	protected synchronized void clearChanged() {
	    changed = false;
	}
	...
}

有源码可值,只有changed为真时,才会通知观察者。changed标志是十分有用的,setChanged方法可以让我们在更新观察者是,有跟多的弹性,我们可以更适当的通知观察者。比如我们制定一定的规则,只有当满足条件时,我们才调用setChanged方法,进行有效的更新。

若是我们使用的是notifyObservers(Object arg)方法,那么就采用了 数据的方式。

观察者:

public class CurrentConditionsDisplay implements java.util.Observer, DisplayElement{
    private Observable weatherData;

    public CurrentConditionsDisplay(Observable weatherData) {
        this.weatherData = weatherData;
        weatherData.addObserver(this);
    }

    @Override
    public void display(Data data) {
        System.out.println("CurrentDisplay:" + data.getTemperature() + "F degrees and " + data.getHumidity() + "% humidity");
    }

    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof WeatherData){
            display((Data)arg);
        }
    }
}

java.util.Observer接口定义的update接受两个参数:一个Observable对象,供我们判断是哪个可观察者送出了通知;一个Object对象,这对应于notifyObservers(Object arg)中的arg

拉数据

以上是推数据,我们再做一点改变,就可以实现观察者自己拉数据:

可观察者:

public class WeatherData extends Observable {

    private List<Observer> observers;
    private Data data;
    public WeatherData() {
        observers = new ArrayList<>();
        data = new Data();
    }

    public Data getData() {
        return data;
    }

    public void measurementsChanged(){
        setChanged();
        notifyObservers();
    }
    public void setMeasurements(float temperature,float humidity,float pressure){
        data.setTemperature(temperature);
        data.setHumidity(humidity);
        data.setPressure(pressure);
        measurementsChanged();
    }
}

我们为可观察者的状态data添加了访问器,并且在通知观察者时,调用notifyObservers()方法,没有传送具体的数据给观察者。

观察者:

public class CurrentConditionsDisplay implements java.util.Observer, DisplayElement{
    private Observable weatherData;

    public CurrentConditionsDisplay(Observable weatherData) {
        this.weatherData = weatherData;
        weatherData.addObserver(this);
    }

    @Override
    public void display(Data data) {
        System.out.println("CurrentDisplay:" + data.getTemperature() + "F degrees and " + data.getHumidity() + "% humidity");
    }

    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof WeatherData){
            Data data = (Data) arg;
            if (data == null){
                WeatherData weatherData = (WeatherData)o;
                data = weatherData.getData();//拉取数据
            }
            display(data);
        }
    }
}

观察者这边的update方法中,判断arg为空,那么就主动调用访问器去拉取数据。

使用Java内置的Observable类带来的弊端:

Observable是一个类,这违背了 针对接口编程,而非针对实现编程,这导致我们在使用它时,必须设计一个类继承它。如果某个类想同时具有Observable和另一个超类的行为,就会陷入两难。这限制了Observalbe的复用能力。另外,因为没有Observalbe接口,我们也不能建立自己的实现,和Java内置的ObserverAPI搭配使用。再者Observable.setChanged被修饰为protected,所以我们将Observable实例组合到我们自己的对象中来后,无法调用setChanged方法来正常通知观察者。

标题“51单片机通过MPU6050-DMP获取姿态角例程”解析 “51单片机通过MPU6050-DMP获取姿态角例程”是一个基于51系列单片机(一种常见的8位微控制器)的程序示例,用于读取MPU6050传感器的数据,并通过其内置的数字运动处理器(DMP)计算设备的姿态角(如倾斜角度、旋转角度等)。MPU6050是一款集成三轴加速度计和三轴陀螺仪的六自由度传感器,广泛应用于运动控制和姿态检测领域。该例程利用MPU6050的DMP功能,由DMP处理复杂的运动学算法,例如姿态融合,将加速度计和陀螺仪的数据进行整合,从而提供稳定且实时的姿态估计,减轻主控MCU的计算负担。最终,姿态角数据通过LCD1602显示屏以字符形式可视化展示,为用户提供直观的反馈。 从标签“51单片机 6050”可知,该项目主要涉及51单片机和MPU6050传感器这两个关键硬件组件。51单片机基于8051内核,因编程简单、成本低而被广泛应用;MPU6050作为惯性测量单元(IMU),可测量设备的线性和角速度。文件名“51-DMP-NET”可能表示这是一个与51单片机及DMP相关的网络资源或代码库,其中可能包含C语言等适合51单片机的编程语言的源代码、配置文件、用户手册、示例程序,以及可能的调试工具或IDE项目文件。 实现该项目需以下步骤:首先是硬件连接,将51单片机与MPU6050通过I2C接口正确连接,同时将LCD1602连接到51单片机的串行数据线和控制线上;接着是初始化设置,配置51单片机的I/O端口,初始化I2C通信协议,设置MPU6050的工作模式和数据输出速率;然后是DMP配置,启用MPU6050的DMP功能,加载预编译的DMP固件,并设置DMP输出数据的中断;之后是数据读取,通过中断服务程序从DMP接收姿态角数据,数据通常以四元数或欧拉角形式呈现;再接着是数据显示,将姿态角数据转换为可读的度数格
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值