观察者模式,主要就是一个地方的改变,需要通知多个其他依赖方,例如,一个订单的状态发生变更,后续的物流、保险等许多系统都需要用到这个变化值,那么就可以考虑使用观察者模式(这里不考虑跨系统问题)
在JDK中,Swing API就用到了观察者模式,放一个按钮被触发时,所有需要依赖该通知的地方,都会收到通知事件,收到通知事件后,做相应的处理。
在观察者模式中,一般存在三个对象,一个是通知对象,一个是主题Subject,一个是具体的需要被通知的对象。
接下来就演示一个观察者模式
示例:一个天气监测站,每有数据更新,则通知各个显示站,显示站根据自己的需求进行展示内容
首先,创建一个主题接口,用来注册、移除观察者,同时可以通知观察者
package lcl.mm.pattern.observer.demo1; public interface Subject { /** * 注册一个观察者 * @param o */ public void registerObserver(MyObserver o); /** * 移除一个观察者 * @param o */ public void removeOberver(MyObserver o); /** * 通知观察者 */ public void notifyObserver(); }
然后,创建一个展示内容的接口
package lcl.mm.pattern.observer.demo1; public interface DisplayElement { public void display(); }
接着,创建一个更新接口
package lcl.mm.pattern.observer.demo1; public interface MyObserver { public void update(int temp,int humidity, int pressure); }
创建一个通知的对象,该对象实现Subject接口,对注册、移除和通知观察者接口进行实现,观察通知着的实现是循环所有观察者进行调用更新方法,然后还提供一个供天气监测站调用的接口
package lcl.mm.pattern.observer.demo1; import java.util.ArrayList; public class WeatherData implements Subject{ private ArrayList<MyObserver> observers = new ArrayList<>(); private int temperature; private int humidity; private int pressure; @Override public void registerObserver(MyObserver o) { observers.add(o); } @Override public void removeOberver(MyObserver o) { int i = observers.indexOf(o); if(i>=0){ observers.remove(i); } } @Override public void notifyObserver() { for (MyObserver o:observers) { o.update(temperature,humidity,pressure); } } /** * 数据变更 */ public void setChangeed(int temperature,int humidity, int pressure){ this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; this.notifyObserver(); } }
最后就是创建观察者对象,该对象实现数据更新接口和显示接口,在显示接口实现方法中,按照自己的需求显示内容;在更新接口实现方法中,按照自己的需求做数据更新并调用显示方法(这里写了两个观察者)
package lcl.mm.pattern.observer.demo1; import lombok.extern.slf4j.Slf4j; @Slf4j public class CurrentConditionDisplay implements MyObserver,DisplayElement { private int temperature; private int humidity; private int pressure; private WeatherData weatherData; @Override public void display() { log.info("CurrentConditionDisplay:{},{},{}",temperature,humidity,pressure); } @Override public void update(int temp, int humidity, int pressure) { this.temperature = temp; this.humidity = humidity; this.pressure = pressure; display(); } public CurrentConditionDisplay(WeatherData weatherData){ this.weatherData = weatherData; weatherData.registerObserver(this); } }
package lcl.mm.pattern.observer.demo1; import lombok.extern.slf4j.Slf4j; @Slf4j public class TotalConditionDisplay implements MyObserver,DisplayElement { private int temperature; private int humidity; private int pressure; private WeatherData weatherData; @Override public void display() { log.info("TotalConditionDisplay:{},{},{}",temperature,humidity,pressure); } @Override public void update(int temp, int humidity, int pressure) { this.temperature = temp*10; this.humidity = humidity*10; this.pressure = pressure*10; display(); } public TotalConditionDisplay(WeatherData weatherData){ this.weatherData = weatherData; weatherData.registerObserver(this); } }
最后,就是测试,写一个测试方法,通知上述两个观察者,然后再演示剔除一个观察者后再通知所有观察者
@Test void observerTest1(){ WeatherData weatherData = new WeatherData(); CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData); TotalConditionDisplay totalConditionDisplay = new TotalConditionDisplay(weatherData); log.info("register observer:CurrentConditionDisplay,TotalConditionDisplay"); weatherData.setChangeed(1,2,3); weatherData.setChangeed(4,5,6); log.info("remove register currentConditionDisplay"); weatherData.removeOberver(currentConditionDisplay); weatherData.setChangeed(5,6,7); }
输出结果
其实,在jdk中,已经提供了观察者模式的相关类java.util.Observable和java.util.Observer,那么使用JDK提供的实现类来写上述观察者模式,写法如下:
显示的接口,与上述一致
package lcl.mm.pattern.observer.demo2; public interface DisplayElement2 { public void display(); }
创建一个传输数据对象,该对象需要继承java.util.Observable,在该类的调用前notifyObservers()通知观察者之前,需要先调用setChanged();将变更状态置为true,否则不会通知观察者。这是为了给我们提供一个灵活的更新限制,比如说,小于5的情况下,我们就不更新。
package lcl.mm.pattern.observer.demo2; import java.util.Observable; public class WeatherData2 extends Observable { private int temperature; private int humidity; private int pressure; public void changed(int temperature, int humidity, int pressure){ this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; notifyChanged(); } private void notifyChanged(){ setChanged(); notifyObservers(); } public int getTemperature(){ return temperature; } public int getHumidity(){ return humidity; } public int getPressure(){ return pressure; } }
然后,写两个观察者,需要实现java.util.Observer接口和自己创建的展示接口
package lcl.mm.pattern.observer.demo2; import lombok.extern.slf4j.Slf4j; import java.util.Observable; import java.util.Observer; @Slf4j public class CurrentConditionDisplay2 implements Observer, DisplayElement2 { private int temperature; private int humidity; private int pressure; private Observable observable; @Override public void display() { log.info("CurrentConditionDisplay2:{},{},{}",temperature,humidity,pressure); } public CurrentConditionDisplay2(Observable observable){ this.observable = observable; observable.addObserver(this); } @Override public void update(Observable o, Object arg) { if(o instanceof WeatherData2){ WeatherData2 weatherData = (WeatherData2) o; this.temperature = weatherData.getTemperature(); this.humidity = weatherData.getHumidity(); this.pressure = weatherData.getPressure(); display(); } } }
package lcl.mm.pattern.observer.demo2; import lombok.extern.slf4j.Slf4j; import java.util.Observable; import java.util.Observer; @Slf4j public class TotalConditionDisplay2 implements Observer, DisplayElement2 { private int temperature; private int humidity; private int pressure; private Observable observable; @Override public void display() { log.info("TotalConditionDisplay2:{},{},{}",temperature,humidity,pressure); } public TotalConditionDisplay2(Observable observable){ this.observable = observable; observable.addObserver(this); } @Override public void update(Observable o, Object arg) { if(o instanceof WeatherData2){ WeatherData2 weatherData = (WeatherData2) o; this.temperature = weatherData.getTemperature(); this.humidity = weatherData.getHumidity(); this.pressure = weatherData.getPressure(); display(); } } }
最后,编写测试方法
@Test void observerTest2(){ WeatherData2 weatherData = new WeatherData2(); log.info("register observer:CurrentConditionDisplay,TotalConditionDisplay"); CurrentConditionDisplay2 currentConditionDisplay2 = new CurrentConditionDisplay2(weatherData); TotalConditionDisplay2 totalConditionDisplay2 = new TotalConditionDisplay2(weatherData); weatherData.changed(1,2,3); weatherData.changed(4,5,6); log.info("remove register currentConditionDisplay"); weatherData.deleteObserver(currentConditionDisplay2); weatherData.changed(7,8,9); }
测试: