观察者模式

设计模式之观察者模式
观察者模式

1. 概念

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

观察者模式面向的需求是:A对象(观察者)对B对象(被观察者)的某种变化高度敏感,需要在B变化的一瞬间做出反应

举个例子,新闻里喜闻乐见的警察抓小偷,警察需要在小偷伸手作案的时候实施抓捕。在这个例子里,警察是观察者、小偷是被观察者,警察需要时刻盯着小偷的一举一动,才能保证不会错过任何瞬间

程序里的观察者和这种真正的【观察】略有不同,观察者不需要时刻盯着被观察者(例如A不需要每隔1ms就检查一次B的状态),而是采用注册(Register)或者成为订阅(Subscribe)的方式告诉被观察者:我需要你的某某状态,你要在它变化时通知我

采取这样被动的观察方式,既省去了反复检索状态的资源消耗,也能够得到最高的反馈速度

观察者模式通常基于SubjectObserver接口类来设计,下面是是类图
在这里插入图片描述
设计动机
系统在运行的时候,各个相关模块之间需要保证一致性,而又不希望为了保证这种一致性而增强了模块间的耦合关系

观察者(Observer)模式的定义:

指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

观察者模式是一种对象行为型模式,其主要优点如下

  1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系
  2. 目标与观察者之间建立了一套触发机制

它的主要缺点如下

  1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用
  2. 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率

2. 一个例子

2.1 场景

我们接到一个来自气象局的需求:气象局需要我们构建一套系统,这系统有两个公告牌,分别用于显示当前的实时天气和未来几天的天气预报。当气象局发布新的天气数据(WeatherData)后,两个公告牌上显示的天气数据必须实时更新。气象局同时要求我们保证程序拥有足够的可扩展性,因为后期随时可能要新增新的公告牌

2.2 概况

这套系统中主要包括三个部分:气象站(获取天气数据的物理设备)、WeatherData(追踪来自气象站的数据,并更新公告牌)、公告牌(用于展示天气数据)
在这里插入图片描述
WeatherData知道如何跟气象站联系,以获得天气数据。当天气数据有更新时,WeatherData会更新两个公告牌用于展示新的天气数据

2.3 错误的想法

WeatherData 中内置两个公告牌对象,当数据更新的时候调用公告板的更新方法更新公告版,也就是说WeatherData 将数据推送到公告板

public class WeatherData {

    //实例变量声明
    CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(); 
    ForecastDisplay forecastDisplay = new forecastDisplay();
    public void measurementsChanged() {
    
        float temperature = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();        
        //更新公告牌 推送
        currentConditionsDisplay.update(temperature, humidity, pressure);
        forecastDisplay.update(forecastTemperatures);
    }
    ...
}

上面这段代码是典型的针对实现编程,这会导致我们以后增加或删除公告牌时必须修改程序,不符合开闭原则
加入我们现在要新增一个公告板,那么需要做以下更改

public class WeatherData {
    //实例变量声明
    CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(); 
    ForecastDisplay forecastDisplay = new forecastDisplay();
    //变化1 加一个成员变量
    Board board = new Bard();
    public void measurementsChanged() {
    
        float temperature = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();        
        //更新公告牌 推送
        currentConditionsDisplay.update(temperature, humidity, pressure);
        forecastDisplay.update(forecastTemperatures);
        //变化2 新增推送
        board.update();
    }
    ...
}

2.4 使用观察者模式

结合开头类图,我们现在将观察者模式应用到WeatherData项目中来。于是有了下面这张类图
在这里插入图片描述
主题接口

/**
* 主题(发布者、被观察者)
*/
public interface Subject {

    /**
     * 注册观察者
     */
    void registerObserver(Observer observer);

    /**
     * 移除观察者
     */
    void removeObserver(Observer observer);

    /**
     * 通知观察者
     */
    void notifyObservers(); 
}

观察者接口

/**
 * 观察者
 */
public interface Observer {
    void update();
    update(float temperature, float humidity, float pressure);
}

公告牌用于显示的公共接口

public interface DisplayElement {
    void display();
}

下面我们再来看看WeatherData是如何实现的

public class WeatherData implements Subject {

    private List<Observer> observers;//所有的观察者

    private float temperature;//温度
    private float humidity;//湿度
    private float pressure;//气压

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

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

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

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(this.temperature, this.humidity, this.pressure);
        }
    }

    public void measurementsChanged() {
        notifyObservers();
    }

    public void setMeasurements(float temperature, float humidity, float pressure, List<Float> forecastTemperatures) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        this.forecastTemperatures = forecastTemperatures;
        measurementsChanged();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }

}

显示当前天气的公告牌CurrentConditionsDisplay

public class CurrentConditionsDisplay implements Observer, DisplayElement {

    private WeatherData weatherData;

    private float temperature;//温度
    private float humidity;//湿度
    private float pressure;//气压

    public CurrentConditionsDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
        this.weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("当前温度为:" + this.temperature + "℃");
        System.out.println("当前湿度为:" + this.humidity);
        System.out.println("当前气压为:" + this.pressure);
    }
	@Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }

    @Override
    public void update() {
        this.temperature = this.weatherData.getTemperature();
        this.humidity = this.weatherData.getHumidity();
        this.pressure = this.weatherData.getPressure();
        display();
    }
}

到这里,我们整个气象局的WeatherData应用就改造完成了

两个公告牌CurrentConditionsDisplayForecastDisplay实现了ObserverDisplayElement接口,在他们的构造方法中会调用WeatherDataregisterObserver方法将自己注册成观察者,这样被观察者WeatherData就会持有观察者的应用,并将它们保存到一个集合中

当被观察者WeatherData状态发送变化时就会遍历这个集合,循环调用观察者公告牌更新数据的方法。后面如果我们需要增加或者删除公告牌就只需要新增或者删除实现了ObserverDisplayElement接口的公告牌就好了。

观察者模式将观察者和主题(被观察者)彻底解耦,主题只知道观察者实现了某一接口(也就是Observer接口)。并不需要观察者的具体类是谁、做了些什么或者其他任何细节。任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现了Observer接口的对象列表

3. JDK内置的观察者模式

JAVA API内置的观察者模式分别是java.util.Observable类和java.util.Observer接口

要实现观察者模式只需要把主题对象继承java.util.Observable类,观察者实现java.util.Observer接口

注意jdk自带的观察者模式是使用继承的方式去实现,一定程度上违背的OO设计的原则:多用组合,少用继承,而且继承带来的问题必然是扩展的麻烦

3.1 源码分析

3.1.1 Observable(被观察者/发布者)
  • 这个类代表一个可观察到的对象

  • 被观察者使用了Vector容器,所以默认按照观察者的添加顺序保存,通知的时候按照相反顺序执行。但是,子类可以改变这些顺序,所以,顺序不保证

  • 被观察者创建时,它的观察者列表是空集合。两个观察者相等的条件是执行equals时返回true

  • 当一个被观察者发生变化后,应用程序可以调用notifyObservers方法,调用所有观察者的update方法让它们注意到这些变化。

  • 注意里面多了一个setChanged方法,只有当我们setChanged了以后才会提醒观察者,而不是对所有的变化都提醒观察者

public class Observable {
    private boolean changed = false;
    // 底层使用数组,并且线程安全
    private Vector<Observer> obs;
 
    /**
     * 构造一个没有观察者的可观察者
     */
    public Observable() {
        obs = new Vector<>();
    }
 
    /**
     * 增加一个观察者,如果已存在,则什么都不做。
     * 如果o为null,抛出空指针异常。
     * @param o 将被添加的观察者
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }
 
    /**
     * 从观察者集合中删除一个观察者。
     * 如果o为null,无影响
     * @param o 将被删除的观察者
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    
    /**
     * 通知所有观察者,相当于notifyObservers(null)
     */
    public void notifyObservers() {
        notifyObservers(null);
    }
 
    /**
     * 通知所有观察者
     * @param o 可作为观察者update方法的参数
     */
    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();
    }
 
    /**
     * 标记当前被观察者已经改变,调用hasChanged方法将返回true
     */
    protected synchronized void setChanged() {
        changed = true;
    }
 
    /**
     * 将当前被观察者标记为没有改变,调用hasChanged方法将返回false,执行notifyObservers时该方法会被自动调用
     */
    protected synchronized void clearChanged() {
        changed = false;
    }
 
    /**
     * 获取当前被观察者是否已经改变
     * @return true or false
     */
    public synchronized boolean hasChanged() {
        return changed;
    }
 
    /**
     * 返回观察者数量
     * @return 观察者数量
     */
    public synchronized int countObservers() {
        return obs.size();
    }
}
3.1.2 Observer(观察者/订阅者)

当想要收到可观察对象变化的通知时,类可以实现Observers接口。该接口只有一个update方法,每当观察到的对象被更改时,都会调用此方法。一个应用程序调用一个可观察到的对象的“notifyObservers”方法,让这个对象的所有观察者都知道这个变化

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

3.2 使用案例

微信公众号提供了最新文章的字段,和发布文章的方法

public class WeChatSubscription extends Observable {
 
	private String newest;
	
	/**
	 * 发布文章
	 * @param title 文章标题
	 */
	public void publish(String title) {
		System.out.println("微信公众号发布了文章:" + title);
		this.newest = title;
		// 设置改变
		setChanged();
		// 提醒观察者
		notifyObservers(title);
	}
	
	/**
	 * 提供数据的获取接口,以便观察者主动拉数据
	 * @return
	 */
	public String getNewest() {
		return newest;
	}
}

订阅者增加了被观察者的引用,以获得更加灵活的用法

public class Subscriber implements Observer {
	
	Observable observable;
	
	/**
	 * 构造器,设置被观察者,并将自己设为观察者
	 * @param observable 被观察者
	 */
	public Subscriber(Observable observable) {
		this.observable = observable;
		observable.addObserver(this);
	}

	/**
	 * 当被观察者改变时,主动调用
	 */
	@Override
	public void update(Observable o, Object arg) {
		System.out.println("订阅者收到新文章:" + arg);
	}
	
	/**
	 * 用于主动从被观察者拉数据
	 * @return 最新文章标题
	 */
	public String get() {
		if (observable instanceof WeChatSubscription) {
			WeChatSubscription weChatSubscription = (WeChatSubscription) observable;
			return weChatSubscription.getNewest();
		}
		throw new UnsupportedOperationException("不支持的操作!");
	}
 
}

写测试

public class Test {
 
	public static void main(String[] args) {
		// 创建可观察者:这里是微信公众号
		WeChatSubscription observable = new WeChatSubscription();
		
		// 创建观察者:这里是微信用户
		Subscriber observer = new Subscriber(observable);
		
		// 可观察者发生改变:发布新文章
		observable.publish("观察者模式");
		
		// 观察者自己拉取最新文章
		String title = observer.get();
		System.out.println("观察者自己拉取最新文章:" + title);
 
	}
 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值