企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
#### **一、观察者模式简单实现(一)之追剧** > 这里举一个追剧的例子,平常为了不错过最新的电视剧我们会订阅或关注这个电视剧,当电视剧更新后会第一时间推送给我们,下来就简单实现一下。 >[info] 注意,主题中的方法可能与我们前面的图示中的不一样,当然具体问题具体分析。 * **抽象观察者类(Observer)** ~~~ /** * 抽象观察者类,为所有具体观察者定义一个接口,在得到通知时更新自己 */ public interface Observer { /** * 有更新 * * @param message 消息 */ public void update(String message); } ~~~ * **抽象被观察者(抽象主题)** ~~~ /** * 抽象被观察者类 */ public interface Observable { /** * 推送消息 * * @param message 内容 */ void push(String message); /** * 订阅 * * @param observer 订阅者 */ void register(Observer observer); } ~~~ * **具体的观察者类(ConcreteObserver)** ~~~ /** * 具体的观察者类,也就是订阅者 */ public class User implements Observer { @Override public void update(String message) { System.out.println(name + "," + message + "更新了!"); } // 订阅者的名字 private String name; public User(String name) { this.name = name; } } ~~~ * **具体的被观察者类(具体主题)** ~~~ /** * 具体的被观察者类,也就是订阅的节目 */ public class Teleplay implements Observable{ private List<Observer> list = new ArrayList<Observer>();//储存订阅者 @Override public void push(String message) { for(Observer observer:list){ observer.update(message); } } @Override public void register(Observer observer) { list.add(observer); } } ~~~ * **实现** ~~~ public class Client { public static void main(String[] args) { //被观察者,这里就是用户订阅的电视剧 Teleplay teleplay = new Teleplay(); //观察者,这里就是订阅用户 User user1 = new User("小明"); User user2 = new User("小光"); User user3 = new User("小兰"); //订阅 teleplay.register(user1); teleplay.register(user2); teleplay.register(user3); //推送新消息 teleplay.push("xxx电视剧"); } } ~~~ * **结果** ~~~ 小明,xxx电视剧更新了! 小光,xxx电视剧更新了! 小兰,xxx电视剧更新了! ~~~ **总结**:由上面的代码可以看出实现了一对多的消息推送,推送消息都是依赖Observer和Observable这些抽象类,而User和Teleplay完全没有耦合,保证了订阅系统的灵活性和可扩展性。 **观察者模式主要的作用就是对象解耦,将观察者和被观察者完全隔离,只依赖于Observer和Observable抽象。** #### **二、简单实现(二)之气象监测应用** :-: ![](https://box.kancloud.cn/382630a8883d40bf89a07ebaab918617_937x562.jpg) 图1 气象站 如图所示:系统分为3部分,**气象站**(获取是极其想的物理装置)、**WeatherData对象**(追踪来自气象站的数据,并更新布告板)、**布告板**(显示目前天气状况给用户看)。 **WeatherData对象是主题,布告板是观察者。有3个使用天气数据的布告板:目前状态布告板、气象统计布告板、天气预报布告板。一旦WeatherData有更新,这些布告板必须马上更新。此系统还可以扩展,让开发人员建立定制的布告板。** :-: ![](https://box.kancloud.cn/3a09d9d237ec9a35584dcffbc87ccd2e_676x656.jpg) 图2 气象站具体设计 ##### **具体实现** **Subject.java(抽象主题)** ~~~ package headfirst.observer.weather; public interface Subject { public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyObservers(); } ~~~ **Observer.java(抽象观察者)** ~~~ package headfirst.observer.weather; public interface Observer { public void update(float temp, float humidity, float pressure); } ~~~ **DisplayElement.java** ~~~ package headfirst.observer.weather; public interface DisplayElement { public void display(); } ~~~ **气象站(具体主题)** **WeatherData.java** ~~~ package headfirst.observer.weather; import java.util.*; public class WeatherData implements Subject { private ArrayList observers; private float temperature; private float humidity; private float pressure; public WeatherData() { observers = new ArrayList(); } public void registerObserver(Observer o) { observers.add(o); } public void removeObserver(Observer o) { int i = observers.indexOf(o); if (i >= 0) { observers.remove(i); } } 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(); } // other WeatherData methods here public float getTemperature() { return temperature; } public float getHumidity() { return humidity; } public float getPressure() { return pressure; } } ~~~ **布告板(具体观察者)**:具体的布告板 **目前状况:CurrentConditionsDisplay.java** ~~~ package headfirst.observer.weather; public class CurrentConditionsDisplay implements Observer, DisplayElement { private float temperature; private float humidity; private Subject weatherData; public CurrentConditionsDisplay(Subject weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } public void update(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; display(); } public void display() { System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity"); } } ~~~ **气象统计:StatisticsDisplay.java** ~~~ package headfirst.observer.weather; import java.util.*; public class StatisticsDisplay implements Observer, DisplayElement { private float maxTemp = 0.0f; private float minTemp = 200; private float tempSum= 0.0f; private int numReadings; private WeatherData weatherData; public StatisticsDisplay(WeatherData weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } public void update(float temp, float humidity, float pressure) { tempSum += temp; numReadings++; if (temp > maxTemp) { maxTemp = temp; } if (temp < minTemp) { minTemp = temp; } display(); } public void display() { System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp); } } ~~~ **天气预报:ForecastDisplay.java** ~~~ package headfirst.observer.weather; import java.util.*; public class ForecastDisplay implements Observer, DisplayElement { private float currentPressure = 29.92f; private float lastPressure; private WeatherData weatherData; public ForecastDisplay(WeatherData weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } public void update(float temp, float humidity, float pressure) { lastPressure = currentPressure; currentPressure = pressure; display(); } public void display() { System.out.print("Forecast: "); if (currentPressure > lastPressure) { System.out.println("Improving weather on the way!"); } else if (currentPressure == lastPressure) { System.out.println("More of the same"); } else if (currentPressure < lastPressure) { System.out.println("Watch out for cooler, rainy weather"); } } } ~~~ **测试程序** ~~~ package headfirst.observer.weather; import java.util.*; public class WeatherStation { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData); weatherData.setMeasurements(80, 65, 30.4f); weatherData.setMeasurements(82, 70, 29.2f); weatherData.setMeasurements(78, 90, 29.2f); } } ~~~ 输出结果如下: :-: ![](https://box.kancloud.cn/af6c09a68cd5b8ac340c9b69270873a2_655x306.jpg) 图3 气象站输出结果 如果还需要增加酷热指数布告板,可以如下 **酷热指数:HeatIndexDisplay.java** ~~~ package headfirst.observer.weather; public class HeatIndexDisplay implements Observer, DisplayElement { float heatIndex = 0.0f; private WeatherData weatherData; public HeatIndexDisplay(WeatherData weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } public void update(float t, float rh, float pressure) { heatIndex = computeHeatIndex(t, rh); display(); } private float computeHeatIndex(float t, float rh) { float index = (float)((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) + (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) + (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) + (0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 * (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) + 0.000000000843296 * (t * t * rh * rh * rh)) - (0.0000000000481975 * (t * t * t * rh * rh * rh))); return index; } public void display() { System.out.println("Heat index is " + heatIndex); } } ~~~ 测试(增加酷热指数布告板) **WeatherStationHeatIndex.java** ~~~ package headfirst.observer.weather; import java.util.*; public class WeatherStationHeatIndex { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData); HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData); weatherData.setMeasurements(80, 65, 30.4f); weatherData.setMeasurements(82, 70, 29.2f); weatherData.setMeasurements(78, 90, 29.2f); } } ~~~ 输出结果 :-: ![](https://box.kancloud.cn/40fe3d2b11d5b8cbbb878e73ecdaa7ae_878x366.jpg) 图4 气象站输出结果(增加酷热指数) **二、1、简单实现(二)之气象监测应用(使用Java内置的观察者模式来实现气象站获取数据并实时更新布告板的实例)** 修改后的气象站OO设计如下 :-: ![](https://box.kancloud.cn/a89fca3f62e2aba7a99e613fecb3349c_980x643.jpg) 图5 修改后的气象站OO设计(使用java内置的观察者) **主题(被观察者)** **WeatherData.java** **WeatherData(主题)现在扩展自Observable(被观察者),setChanged()方法用来标记已经改变的事实,好让notifyObservers()知道它被调用时应该更新观察者,如果notifyObservers()之前没有先调用setChanged()方法,观察者就不会被通知。setChanged()方法可以让你在更新观察者时,有更多的弹性,可以适当地通知观察者。另外clearChanged()方法、hasChanged()方法有时也需要被用到。** ~~~ package headfirst.observer.weatherobservable; import java.util.Observable; import java.util.Observer; public class WeatherData extends Observable { private float temperature; private float humidity; private float pressure; public WeatherData() { } public void measurementsChanged() { setChanged(); notifyObservers(); } public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } public float getTemperature() { return temperature; } public float getHumidity() { return humidity; } public float getPressure() { return pressure; } } ~~~ **DisplayElement.java** ~~~ package headfirst.observer.weatherobservable; public interface DisplayElement { public void display(); } ~~~ **观察者** **布告板(具体观察者)** **目前状况:CurrentConditionsDisplay.java** ~~~ package headfirst.observer.weatherobservable; import java.util.Observable; import java.util.Observer; public class CurrentConditionsDisplay implements Observer, DisplayElement { Observable observable; private float temperature; private float humidity; public CurrentConditionsDisplay(Observable observable) { this.observable = observable; observable.addObserver(this); } public void update(Observable obs, Object arg) { if (obs instanceof WeatherData) { WeatherData weatherData = (WeatherData)obs; this.temperature = weatherData.getTemperature(); this.humidity = weatherData.getHumidity(); display(); } } public void display() { System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity"); } } ~~~ **气象统计:StatisticsDisplay.java** ~~~ package headfirst.observer.weatherobservable; import java.util.Observable; import java.util.Observer; public class StatisticsDisplay implements Observer, DisplayElement { private float maxTemp = 0.0f; private float minTemp = 200; private float tempSum= 0.0f; private int numReadings; public StatisticsDisplay(Observable observable) { observable.addObserver(this); } public void update(Observable observable, Object arg) { if (observable instanceof WeatherData) { WeatherData weatherData = (WeatherData)observable; float temp = weatherData.getTemperature(); tempSum += temp; numReadings++; if (temp > maxTemp) { maxTemp = temp; } if (temp < minTemp) { minTemp = temp; } display(); } } public void display() { System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp); } } ~~~ **天气预报:ForecastDisplay.java** ~~~ package headfirst.observer.weatherobservable; import java.util.Observable; import java.util.Observer; public class ForecastDisplay implements Observer, DisplayElement { private float currentPressure = 29.92f; private float lastPressure; public ForecastDisplay(Observable observable) { observable.addObserver(this); } public void update(Observable observable, Object arg) { if (observable instanceof WeatherData) { WeatherData weatherData = (WeatherData)observable; lastPressure = currentPressure; currentPressure = weatherData.getPressure(); display(); } } public void display() { System.out.print("Forecast: "); if (currentPressure > lastPressure) { System.out.println("Improving weather on the way!"); } else if (currentPressure == lastPressure) { System.out.println("More of the same"); } else if (currentPressure < lastPressure) { System.out.println("Watch out for cooler, rainy weather"); } } } ~~~ **酷热指数:HeatIndexDisplay.java** ~~~ package headfirst.observer.weatherobservable; import java.util.Observable; import java.util.Observer; public class HeatIndexDisplay implements Observer, DisplayElement { float heatIndex = 0.0f; public HeatIndexDisplay(Observable observable) { observable.addObserver(this); } public void update(Observable observable, Object arg) { if (observable instanceof WeatherData) { WeatherData weatherData = (WeatherData)observable; float t = weatherData.getTemperature(); float rh = weatherData.getHumidity(); heatIndex = (float) ( (16.923 + (0.185212 * t)) + (5.37941 * rh) - (0.100254 * t * rh) + (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) + (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) + (0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 * (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) + (0.000000000843296 * (t * t * rh * rh * rh)) - (0.0000000000481975 * (t * t * t * rh * rh * rh))); display(); } } public void display() { System.out.println("Heat index is " + heatIndex); } } ~~~ 测试 **WeatherStation.java** ~~~ package headfirst.observer.weatherobservable; public class WeatherStation { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay currentConditions = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData); weatherData.setMeasurements(80, 65, 30.4f); weatherData.setMeasurements(82, 70, 29.2f); weatherData.setMeasurements(78, 90, 29.2f); } } ~~~ 输出结果 :-: ![](https://box.kancloud.cn/e65734d9f61a7403976cbc736fce64ed_655x361.jpg) 图6 气象站输出结果(使用Java内置的观察者模式) 增加酷热指数布告板的测试 **WeatherStationHeatIndex.java** ~~~ package headfirst.observer.weatherobservable; public class WeatherStationHeatIndex { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay currentConditions = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData); HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData); weatherData.setMeasurements(80, 65, 30.4f); weatherData.setMeasurements(82, 70, 29.2f); weatherData.setMeasurements(78, 90, 29.2f); } } ~~~ 从以上的图3和图6的结果对比来看,差别是文字输出的次序不一样了。原因在于Observable实现了它的notifyObservers()方法,导致了通知观察者的次序不同于先前的次序,尽管谁也没有错,但是如果我们的代码依赖这样的次序就是错的,因为一旦观察者/可观察者的实现有所改变,通知次序就会改变,很可能就会产生错误的结果。这不是我们所认为的松耦合。 #### **java.util.Observable的缺点** * **Observable(被观察者)是一个类** **Observable是一个类,不是一个接口,甚至没有实现一个接口**,如果想同时具有Observable类和另一个类的行为,就陷入两难,因为Java不支持多继承。再者,没有Observable接口,所以无法建立自己的实现,和Java内置的Observe API搭配使用,也无法将java.util的实现换成另一套做法的实现。 * **Observable将关键的方法保护起来** 观察Observable的API,可以发现setChanged()方法被保护起来,权限修饰符是protected,意味着:除非继承自Observable,否则你无法创建Observable实例并组合到你自己的对象中。这设计违反了第二个设计原则:“多用组合,少用继承”。