一、观察者模式简介
一个软件系统常常要求在某一个对象的状态发生变化的时候,某些其他的对象做出相应的改变。做到这一点的设计方案有很多,但是为了使系统能够易于复用,应该选择低耦合度的设计方案。减少对象之间的耦合有利于系统的复用,但是同时设计师需要使这些低耦合度的对象之间能够维持行动的协调一致,保证高度的协作。观察者模式是满足这一要求的各种设计方案中最重要的一种。
观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。实际上,观察者模式又被成为发布/订阅模式,在这种模式中,一个目标物件(被观察者)管理所有相依于它的相关物件(观察者),并且在目标物件的状态改变时主动发出通知。这通常透过使用各相关物件所提供的方法来实现,观察者模式模式通常被用来作事件处理系统。
主要解决的问题
将一个系统分割成一个一些类相互协作的类有一个不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。观察者就是解决这类的耦合关系的。
观察者模式所涉及的角色有:
抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。
具体主题(ConcreteSubject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。
抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。
实现观察者模式使用三个类 Subject、Observer 和 Client。Subject 对象带有绑定观察者到 Client 对象和从 Client 对象解绑观察者的方法。我们需要创建 Subject 类、Observer 抽象类和扩展抽象类 Observer 的实体类。
适用场景
当一个对象的改变需要给变其它对象时,而且它不知道具体有多少个对象有待改变时。
一个抽象某型有两个方面,当其中一个方面依赖于另一个方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。
二、示例演示
1、业务需求
客户有一个WeatherData对象,负责追踪温度、湿度和气压等数据。现在客户给我们提了个需求,让我们利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。
WeatherData对象提供了4个接口:
getTemperature():获取温度
getHumidity():获取湿度
getPressure():获取气压
measurementsChanged():一旦气象测量更新,此方法会被调用
我们的工作是实现measurementsChanged(),好让它更新目前状况、气象统计、天气预报的显示布告板。
利用观察者模式设计气象站系统
我们可以把WeatherData对象当作主题,把布告板当作观察者。布告板为了取得信息,就必须先向WeatherData对象注册。一旦WeatherData知道有某个布告板的存在,就会适时地调用布告板的某个方法来告诉布告板观测值是多少。每个布告板都应该有一个大概名为update()的方法,以供WeatherData对象调用。
2、定义主题接口和其具体实现
主题接口:
public interface Subject{
// registerObserver和removeObserver都需要一个观察者作为变量,该观察者是用来注册或被删除的
void registerObserver(Observer o);
void removeObserver(Observer o);
// 当主题状态改变时。这个方法会被调用,以通知所有的观察者
void notifyObservers();
}具体的实现类:
// WeatherData现在实现了Subject接口
public class WeatherData implements Subject {
// 我们加上一个ArrayList来记录观察者,此ArrayList是在构造器中建立的。
public ArrayList observers;
//记录温度、湿度和气压
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList();
}
// 当注册观察者时,我们只要把它加到ArrayList的后面即可
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
// 当观察者想取消注册,我们把它从ArrayList中删除即可
@Override
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if (i >= 0) {
observers.remove(i);
}
}
// 在这里,我们把状态告诉每一个观察者
// 因为观察者都实现了update(),所以我们知道如何通知它们
@Override
public void notifyObservers() {
for (int i = 0; i < observers.size(); i++) {
Observer observer = (Observer) observers.get(i);
observer.update(temperature, humidity, pressure);
}
}
// 当从气象站得到更新观测值时,我们通知观察者
public void measurementsChanged(){
notifyObservers();
}
//当我们调用这个方法更新数据时,会自动通知所有观察者
public void setMeasurements(float temperature, float humidity, float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}3、定义观察者接口和其具体实现
观察者接口:
public interface Observer {
// 当气象观测值改变时,主题会把温度、湿度、气压的状态值当作方法的参数,传送给观察者
public void update(float temp, float humidity, float pressure);
}操作方法的接口:
public interface DisplayElement {
// 当布告板需要显示时,调用此方法
public void display();
}观察者的实现类:
// 此布告板实现了Observer接口,所以可以从WeatherData对象中获得改变
// 它也实现了DisplayElement接口,因为我们的API规定所有的布告板都必须实现此接口
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;
// 构造器需要WeatherData对象(也就是主题)作为注册之用
public CurrentConditionsDisplay(Subject weatherData){
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
// display()方法就是把最近的温度和湿度显示出来
@Override
public void display() {
System.out.println("Cuttent conditions:" + temperature + "F degrees and " + humidity + "% humidity");
}
// 当update()被调用时,我们把温度和湿度保存起来,然后调用display()
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
}4、编写测试方法
public class WeatherStation {
public static void main(String[] args){
// 建立WeatherData对象
WeatherData weatherData = new WeatherData();
// 建立目前状况布告板
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);//这一步完成了观察者的注册操作。
// 模拟新的气象测量
//每次调用weatherData的setMeasurements时,会遍历调用所有观察者的update方法。
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}5、运行结果
Cuttent conditions: 80.0 F degrees and 65% humidity
Cuttent conditions: 82.0 F degrees and 70% humidity
Cuttent conditions: 78.0 F degrees and 90% humidity
三、JAVA提供的对观察者模式的支持
1、JAVA中的内置接口和类实现
在JAVA语言的java.util库里面,提供了一个Observable类以及一个Observer接口,构成JAVA语言对观察者模式的支持。
Observer接口只定义了一个方法,即update()方法,当被观察者对象的状态发生变化时,被观察者对象的notifyObservers()方法就会调用这一方法。
public interface Observer {
void update(Observable o, Object arg);
}Observable类:被观察者类都是java.util.Observable类的子类。java.util.Observable提供公开的方法支持观察者对象,这些方法中有两个对Observable的子类非常重要:一个是setChanged(),另一个是notifyObservers()。第一方法setChanged()被调用之后会设置一个内部标记变量,代表被观察者对象的状态发生了变化。第二个是notifyObservers(),这个方法被调用时,会调用所有登记过的观察者对象的update()方法,使这些观察者对象可以更新自己。
public class Observable {
private boolean changed = false;
private Vector obs;
/** Construct an Observable with zero Observers. */
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);
}
/**
* 如果本对象有变化(那时hasChanged 方法会返回true)
* 调用本方法通知所有登记的观察者,即调用它们的update()方法
* 传入this和arg作为参数
*/
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();
}
/**
* 将“已变化”设置为true
*/
protected synchronized void setChanged() {
changed = true;
}
/**
* 将“已变化”重置为false
*/
protected synchronized void clearChanged() {
changed = false;
}
/**
* 检测本对象是否已变化
*/
public synchronized boolean hasChanged() {
return changed;
}
/**
* Returns the number of observers of this <tt>Observable</tt> object.
*
* @return the number of observers of this object.
*/
public synchronized int countObservers() {
return obs.size();
}
}这个类代表一个被观察者对象,有时称之为主题对象。一个被观察者对象可以有数个观察者对象,每个观察者对象都是实现Observer接口的对象。在被观察者发生变化时,会调用Observable的notifyObservers()方法,此方法调用所有的具体观察者的update()方法,从而使所有的观察者都被通知更新自己。
2、怎样使用JAVA对观察者模式的支持
这里假设被观察对象叫做Watched;而观察者对象叫做Watcher。Watched对象继承自java.util.Observable类;而Watcher对象实现了java.util.Observer接口。另外有一个Test类扮演客户端角色。
被观察者Watched类源代码
public class Watched extends Observable{
private String data = "";
public String getData() {
return data;
}
public void setData(String data) {
if(!this.data.equals(data)){
this.data = data;
setChanged();
}
notifyObservers();
}
}观察者类Watcher源代码
public class Watcher implements Observer{
public Watcher(Observable o){
o.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
System.out.println("状态发生改变:" + ((Watched)o).getData());
}
}
测试类源代码
public class Test {
public static void main(String[] args) {
//创建被观察者对象
Watched watched = new Watched();
//创建观察者对象,并将被观察者对象登记
Observer watcher = new Watcher(watched);
//给被观察者状态赋值
watched.setData("start");
watched.setData("run");
watched.setData("stop");
}
}
Test对象首先创建了Watched和Watcher对象。在创建Watcher对象时,将Watched对象作为参数传入;然后Test对象调用Watched对象的setData()方法,触发Watched对象的内部状态变化;Watched对象进而通知实现登记过的Watcher对象,也就是调用它的update()方法。
四、总结
1、观察者模式的适用场景:
(1)当一个抽象模型有两个方面,其中一个方面依赖另一个方面,这时使用观察者模式可以将这两个者封装在独立的对象中使他们各自独立的改变和复用。
(2)当一个对象的改变要影响到其他对象,而且不知道有多少的对象需要改变。
(3)当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
2、观察者模式的优点
(1)使用面向对象的抽象,观察者模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达到松耦合。
(2)目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是否需要订阅通知。目标对象对此一无所知。
转载请注明原文地址: https://ju.6miu.com/read-20740.html