观察者模式

2019-01-24 748 ℃

定义

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

结构

  • 抽象主题(Subject): 抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。抽象主题角色又叫做抽象被观察者角色。
  • 具体主题(ConcreteSubject): 该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。具体主题角色又叫做具体被观察者角色。
  • 抽象观察者(Observer): 为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
  • 具体的观察者(ConcreteObserver): 具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。

优点

  • 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
  • 观察者模式在观察目标和观察者之间建立一个抽象的耦合。
  • 观察者模式符合“开闭原则”的要求。

缺点

  • 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

适用场景

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

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

  • 一个对象必须通知其他对象,而并不知道这些对象是谁。

  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

推模型和拉模型

  • 推模型:主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。
  • 拉模型:主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。

代码实现

设计一个气象站和多个布告板,并且布告板在将来会继续增加。布告板会在天气信息发生改变时更新其内容。

推模型

被观察者接口(抽象主题)

public interface Subject { /** * 注册观察者 * @param o 需要注册的观察者对象 */ public void registerObserver(Observer o); /** * 删除观察者 * @param o 需要删除的观察者对象 */ public void removeObserver(Observer o); /** * 通知所有观察者 */ public void notifyObservers(); }

观察者接口(抽象观察者)

public interface Observer { /** * 当气象观测值改变时,主题会把这些状态值当做方法的参数,传递给观察者 * @param temp 温度 * @param humidity 湿度 * @param pressure 气压 */ public void update(float temp, float humidity, float pressure); }

气象数据(具体主题)

public class WeatherData implements Subject { // 观察者集合 private ArrayList<Observer> observers; // 温度 private float temperature; // 湿度 private float humidity; // 气压 private float pressure; public WeatherData() { observers = new ArrayList<Observer>(); } @Override public void registerObserver(Observer o) { observers.add(o); } @Override public void removeObserver(Observer o) { int i = observers.indexOf(o); if (i >= 0) { observers.remove(i); } } @Override public void notifyObservers() { for (Observer observer : observers) { //将所有数据推给观察者 observer.update(temperature, humidity, pressure); } } /** * 更新观测值 * @param temperature 温度 * @param humidity 湿度 * @param pressure 气压 */ public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; //通知所有观察者更新数据 notifyObservers(); } }

布告板(具体观察者)

/** * 显示当前天气状态的布告板 */ public class CurrentConditionsDisplay implements Observer { // 温度 private float temperature; // 湿度 private float humidity; // 主题 private Subject weatherData; public CurrentConditionsDisplay(Subject weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } @Override public void update(float temperature, float humidity, float pressure) { System.out.println("当前气象数据:温度:"+temperature+"℃,湿度:"+humidity+"%"); } }
/** * 进行天气预报的布告板 */ public class ForecastDisplay implements Observer { //当前气压 private float currentPressure = 29.92f; //最后一次更新时的气压 private float lastPressure; // 主题 private WeatherData weatherData; public ForecastDisplay(WeatherData weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } @Override public void update(float temp, float humidity, float pressure) { lastPressure = currentPressure; currentPressure = pressure; System.out.print("天气预报: "); if (currentPressure > lastPressure) { System.out.println("天气正在变好"); } else if (currentPressure == lastPressure) { System.out.println("天气将继续保持"); } else if (currentPressure < lastPressure) { System.out.println("天气转凉,注意保暖"); } } }

测试类

public class Test { public static void main(String[] args) { //创建主题 WeatherData weatherData = new WeatherData(); //显示当前天气状态的布告板 CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); //进行天气预报的布告板 ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData); //更新数据 weatherData.setMeasurements(80, 65, 30.4f); System.out.println(); weatherData.setMeasurements(82, 70, 29.2f); //删除进行天气预报的布告板 weatherData.removeObserver(forecastDisplay); System.out.println(); weatherData.setMeasurements(78, 90, 29.2f); } }

输出结果

当前气象数据:温度:80.0℃,湿度:65.0%
天气预报: 天气正在变好

当前气象数据:温度:82.0℃,湿度:70.0%
天气预报: 天气转凉,注意保暖

当前气象数据:温度:78.0℃,湿度:90.0%

拉模型

被观察者接口(抽象主题)

public class Subject { /** * 观察者集合 */ private List<Observer> observers=new ArrayList<>(); /** * 注册观察者 * @param o 需要注册的观察者对象 */ public void registerObserver(Observer o){ if (o!=null&&!observers.contains(o)) { observers.add(o); } } /** * 删除观察者 * @param o 需要删除的观察者对象 */ public void removeObserver(Observer o){ observers.remove(o); } /** * 通知所有观察者 */ public void notifyObservers(){ for (Observer observer : observers) { //将所有数据推给观察者 observer.update(this); } } }

观察者接口(抽象观察者)

public interface Observer { /** * 当气象观测值改变时,将主题自身的引用,传递给观察者,观察者按需取出数据 * @param subject 被观察者 */ public void update(Subject subject); }

气象数据(具体主题)

public class WeatherData extends Subject { // 温度 private float temperature; // 湿度 private float humidity; // 气压 private float pressure; /** * 更新观测值 * @param temperature 温度 * @param humidity 湿度 * @param pressure 气压 */ public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; //通知所有观察者更新数据 notifyObservers(); } public float getTemperature() { return temperature; } public float getHumidity() { return humidity; } public float getPressure() { return pressure; } }

布告板(具体观察者)

/** * 显示当前天气状态的布告板 */ public class CurrentConditionsDisplay implements Observer { // 温度 private float temperature; // 湿度 private float humidity; public CurrentConditionsDisplay(Subject weatherData) { weatherData.registerObserver(this); } @Override public void update(Subject subject) { if (subject instanceof WeatherData) { WeatherData weatherData = (WeatherData)subject; this.temperature=weatherData.getTemperature(); this.humidity=weatherData.getHumidity(); display(); } } public void display() { System.out.println("当前气象数据:温度:"+temperature+"℃,湿度:"+humidity+"%"); } }
/** * 进行天气预报的布告板 */ public class ForecastDisplay implements Observer { //当前气压 private float currentPressure = 29.92f; //最后一次更新时的气压 private float lastPressure; public ForecastDisplay(WeatherData weatherData) { weatherData.registerObserver(this); } @Override public void update(Subject subject) { if (subject instanceof WeatherData) { WeatherData weatherData = (WeatherData)subject; this.lastPressure=currentPressure; this.currentPressure=weatherData.getPressure(); display(); } } public void display() { System.out.print("天气预报: "); if (currentPressure > lastPressure) { System.out.println("天气正在变好"); } else if (currentPressure == lastPressure) { System.out.println("天气将继续保持"); } else if (currentPressure < lastPressure) { System.out.println("天气转凉,注意保暖"); } } }

测试类

public class Test { public static void main(String[] args) { //创建主题 WeatherData weatherData = new WeatherData(); //显示当前天气状态的布告板 CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); //进行天气预报的布告板 ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData); //更新数据 weatherData.setMeasurements(80, 65, 30.4f); System.out.println(); weatherData.setMeasurements(82, 70, 29.2f); //删除进行天气预报的布告板 weatherData.removeObserver(forecastDisplay); System.out.println(); weatherData.setMeasurements(78, 90, 29.2f); } }

输出结果

当前气象数据:温度:80.0℃,湿度:65.0%
天气预报: 天气正在变好

当前气象数据:温度:82.0℃,湿度:70.0%
天气预报: 天气转凉,注意保暖

当前气象数据:温度:78.0℃,湿度:90.0%

JDK内置的观察者模式

Java API中有内置的观察者模式供我们使用,java.util包(package)内包含基本的Observer接口与Observer类,我们可以使用推(push)或拉(pull)的方式传送数据。

气象数据,继承java.util.Observable类(具体主题),温度和湿度采用拉(pull)模型传递数据,气压使用推(push)模型传递数据

public class WeatherData extends Observable { // 温度 private float temperature; // 湿度 private float humidity; // 气压 private float pressure; public WeatherData() { } /** * 从气象站得到更新观测值时,通知观察者 */ public void measurementsChanged() { //标记状态为已改变 setChanged(); //通知观察者更新数据。如果没有标记状态为已改变,则不会发送通知 notifyObservers(pressure); } /** * 更新观测值 * @param temperature 温度 * @param humidity 湿度 * @param pressure 气压 */ public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } /** * 拉取温度 * @return */ public float getTemperature() { return temperature; } /** * 拉取湿度 * @return */ public float getHumidity() { return humidity; } }

布告板,实现java.util.Observer接口(具体观察者)

/** * 显示当前天气状态的布告板 */ public class CurrentConditionsDisplay implements Observer { // 被观察者 Observable observable; // 温度 private float temperature; // 湿度 private float humidity; public CurrentConditionsDisplay(Observable observable) { this.observable = observable; observable.addObserver(this); } /** * 采用拉方法更新数据,每个布告板只获取自己所需要的信息 * @param obs 被观察的对象 * @param arg 传递给 notifyObservers方法的参数 */ @Override public void update(Observable obs, Object arg) { if (obs instanceof WeatherData) { WeatherData weatherData = (WeatherData)obs; this.temperature = weatherData.getTemperature(); this.humidity = weatherData.getHumidity(); System.out.println("当前气象数据:温度:"+temperature+"℃,湿度:"+humidity+"%"); } } }
/** * 进行天气预报的布告板 */ public class ForecastDisplay implements Observer { //当前气压 private float currentPressure = 29.92f; //最后一次更新时的气压 private float lastPressure; public ForecastDisplay(Observable observable) { observable.addObserver(this); } /** * 采用推方法更新气压 * @param observable 被观察的对象 * @param arg 传递给 notifyObservers方法的参数(气压) */ @Override public void update(Observable observable, Object arg) { lastPressure = currentPressure; currentPressure = (float)arg; System.out.print("天气预报: "); if (currentPressure > lastPressure) { System.out.println("天气正在变好"); } else if (currentPressure == lastPressure) { System.out.println("天气将继续保持"); } else if (currentPressure < lastPressure) { System.out.println("天气转凉,注意保暖"); } } }

测试

public class Test { public static void main(String[] args) { //创建主题 WeatherData weatherData = new WeatherData(); //显示当前天气状态的布告板 CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); //进行天气预报的布告板 ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData); //更新数据 weatherData.setMeasurements(80, 65, 30.4f); System.out.println(); weatherData.setMeasurements(82, 70, 29.2f); //删除显示当前天气状态的布告板 weatherData.deleteObserver(currentDisplay); System.out.println(); weatherData.setMeasurements(78, 90, 29.2f); } }

输出结果

天气预报: 天气正在变好
当前气象数据:温度:80.0℃,湿度:65.0%

天气预报: 天气转凉,注意保暖
当前气象数据:温度:82.0℃,湿度:70.0%

天气预报: 天气将继续保持
版权声明:周华个人博客原创文章,转载请注明出处。

文章链接:http://www.iszhouhua.com/observer-pattern.html

发表时间:2019-01-24 19:20

最后更新时间:2019-03-04 23:25