本文将会通过一个小Demo来记述使用观察者模式的好处以及Java中内置的观察者模式。
Demo描述:
气象站提供数据:温度、气压、和湿度。这时候有第三方公司需要接入今天的数据,来展示在公司的公告板上。气象站测量数据更新时需要实时更新通知给第三方。
通常的设计解决方案:采用OO原则:公报板CurrentCoditions的update()方法调用datachage()方法,然后通过display()方法去展示数据内容。(OO原则过于简单,不做详细描述)
CurrentCoditions.java:(公告板)
public class CurrentCoditions {
private float Temperature;
private float Pressure;
private float Humidity;
public void update(float Temperature,float Pressure,float Humidity){
this.Temperature=Temperature;
this.Pressure=Pressure;
this.Humidity=Humidity;
display();
}
public void display(){
System.out.println("*****公告板:******");
System.out.println("今日的温度:"+Temperature);
System.out.println("今日的气压:"+Pressure);
System.out.println("今日的湿度:"+Humidity);
}
}
WeatherData.java:
public class WeatherData {
private float Temperature;
private float Pressure;
private float Humidity;
private CurrentCoditions currentCoditions;
public WeatherData(CurrentCoditions currentCoditions){
this.currentCoditions= currentCoditions;
}
public float getTemperature() {
return Temperature;
}
public float getPressure() {
return Pressure;
}
public float getHumidity() {
return Humidity;
}
public void dataChange(){
currentCoditions.update(getTemperature(),getPressure(),getHumidity());
}
//假装气象站调用了这个信息,设置参数(模拟气象站有新的数据并通知)
public void setData(float Temperature,float Pressure,float Humidity){
this.Temperature=Temperature;
this.Pressure=Pressure;
this.Humidity=Humidity;
dataChange();
}
}
InternetWeather.java:(气象站)
public class InternetWeather {
public static void main(String[] args){
CurrentCoditions currentCoditions;//公告板
WeatherData weatherData;
currentCoditions=new CurrentCoditions();
weatherData=new WeatherData(currentCoditions);
weatherData.setData(30,150,40);
}
}
新需求来啦:当前这个公司的公报版做好了。另一个公司说我想做一个天气预报的API接口,然后我也做个预报下个礼拜的公报版。需要设计开放性API,便于第三方也能接入气象站。
设想用传统的OO原则去解决新需求会产生什么问题?
预报公告板,内容肯定和今日公告板不同。所以,如果设计一个预告公告板,岂不是要新加一个预告公告板类,然后在WeatherData中又去加入该类呢。如果还有其他不只一家公司呢。明显,OO原则不适用。
解决方案: 不能每次都修改weatherData.因为每加入一个公司,都要增加一个公告板,即构造函数都要增加参数,每次都需要改和重新编译。所以,分析当前的变化部分与不变化部分。公报版变化所以抽象为接口和实现。
接口函数weatherdata有了公告板也要不停的变化,所以也要抽象为接口和实现。
1):其他第三方公司接入气象站获取数据的问题
2):无法在运行时动态的添加第三方
看我们,如何用观察者模式巧妙的解决它!(代码分析在下方,先贴源码)
Observer.java:【接口】(观察者)
package newSolution;
public interface Observer {
public void update(float Temperature,float Pressure,float Humidity);
}
Subject.java:【接口】(实现注册、移除和通知观察者)
package newSolution;
public interface Subject {
public void registerObserver(Observer observer);
public void removeObserver(Observer observer);
public void notifyObservers();
}
WeatherData.java:
package newSolution;
import java.util.ArrayList;
public class WeatherData implements Subject {
private float Temperature;
private float Pressure;
private float Humidity;
private ArrayList observers;
public WeatherData(){
observers=new ArrayList();
}
public float getTemperature() {
return Temperature;
}
public float getPressure() {
return Pressure;
}
public float getHumidity() {
return Humidity;
}
public void dataChange(){
notifyObservers();
}
//假装气象站调用了这个信息,设置参数(模拟气象站有新的数据并通知,便于测试)
public void setData(float Temperature,float Pressure,float Humidity){
this.Temperature=Temperature;
this.Pressure=Pressure;
this.Humidity=Humidity;
dataChange();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
if (observers.contains(observer)){
observers.remove(observer);
}
}
@Override
public void notifyObservers() {
for(int i=0,len=observers.size();i
CurrentCoditions.java:(公告板)
package newSolution;
public class CurrentCoditions implements Observer {
private float Temperature;
private float Pressure;
private float Humidity;
@Override
public void update(float Temperature, float Pressure, float Humidity) {
this.Temperature=Temperature;
this.Pressure=Pressure;
this.Humidity=Humidity;
display();
}
public void display(){
System.out.println("*****公告板:******");
System.out.println("今日的温度:"+Temperature);
System.out.println("今日的气压:"+Pressure);
System.out.println("今日的湿度:"+Humidity);
}
}
ForcastConditions.java:(明日天气预告公告版)
package newSolution;
public class ForcastConditions implements Observer {
private float Temperature;
private float Pressure;
private float Humidity;
@Override
public void update(float Temperature, float Pressure, float Humidity) {
this.Temperature=Temperature;
this.Pressure=Pressure;
this.Humidity=Humidity;
display();
}
public void display(){
System.out.println("*****公告板:******");
System.out.println("明日的温度:"+Temperature);
System.out.println("明日的气压:"+Pressure);
System.out.println("明日的湿度:"+Humidity);
}
}
InternetWeather.java:(气象站)
package newSolution;
public class InternetWeather {
public static void main(String[] args){
CurrentCoditions currentCoditions;//公告板
ForcastConditions forcastConditions;//明日公告板
WeatherData weatherData;
weatherData=new WeatherData();
currentCoditions=new CurrentCoditions();
forcastConditions=new ForcastConditions();
//注册观察者
weatherData.registerObserver(currentCoditions);
weatherData.registerObserver(forcastConditions);
//数据更新
weatherData.setData(37,180,50);
//取消今日公报版
weatherData.removeObserver(currentCoditions);
weatherData.setData(40,250,50);
}
}
代码分析:
首先,我们注册了观察者Observer,它有个数据更新方法,由今日公报版、明日天气预报公告板去实现update()方法,update()方法去调用2个类自己定义的display()方法去实现展示内容。然后就是Subject,它是来实现注册、移除和通知观察者的,它由WeatherData天气数据操控类来实现,在WeatherData中添加了ArrayList
观察者模式到底是啥?
就像牛奶配送商店和顾客。顾客可以打电话给商店说:“以后每周给我送牛奶!”,商店于是把客户的地址和电话记录下来。突然,某天,客户说:“我不想喝牛奶了,不要给我送了~”,商店接收到通知后,就将该用户移除了商店的配送名单中。
(可能代码分析那里有点绕,不过多回顾哈上方的源码,相信也是很好理解的!)
扩展:Java中内置的观察者: 松耦合、高内聚、隔离。
其中,
松耦合(使用于对象之间):可以交互,但不存在依赖。(异步,不需要等待,不需要直到对象的具体实现)(比如subject和observer)。
高内聚(适用于对象之内)。
对于Java内置的观察者,需要了解:
ObserVable:相当于Subject.实行注册监听等。有一点不同的是。它是类,是需要继承它而不是接口。(实现了注册、移除和通知 功能),使用了继承,子类的注册移除和通知等功能就可以不用自己去实现了。
Observer:是接口,实现Update().(推通知和拉通知都是可以的)
setChange()方法一定要调用(为了通知的灵活性)
WeatherData.java:
package Java;
import java.util.Observable;
public class WeatherData extends Observable {
private float Temperature;
private float Pressure;
private float Humidity;
public WeatherData(){
}
public float getTemperature() {
return Temperature;
}
public float getPressure() {
return Pressure;
}
public float getHumidity() {
return Humidity;
}
public void dataChange(){
//java内置的观察者中前面一定要做设置变化
this.setChanged();//源码中有个boolean变量(可以自己调整是否接收)
this.notifyObservers(new Data(getTemperature(),getPressure(),getHumidity()));
}
//假装气象站调用了这个信息,设置参数(模拟气象站有新的数据并通知)
public void setData(float Temperature,float Pressure,float Humidity){
this.Temperature=Temperature;
this.Pressure=Pressure;
this.Humidity=Humidity;
dataChange();
}
public class Data{//存放数据参数
public float Temperature;
public float Pressure;
public float Humidity;
public Data(float temperature,float pressure,float humidity){
Temperature=temperature;
Pressure=pressure;
Humidity=humidity;
}
}
}
CurrentCoditions.java:
package Java;
import java.util.Observable;
import java.util.Observer;
public class CurrentCoditions implements Observer {
private float Temperature;
private float Pressure;
private float Humidity;
@Override
public void update(Observable o, Object arg) {
this.Temperature=((WeatherData.Data)arg).Temperature;
this.Pressure=((WeatherData.Data)arg).Pressure;
this.Humidity=((WeatherData.Data)arg).Humidity;
display();
}
public void display(){
System.out.println("*****公告板:******");
System.out.println("今日的温度:"+Temperature);
System.out.println("今日的气压:"+Pressure);
System.out.println("今日的湿度:"+Humidity);
}
}
ForcastConditions.java:
package Java;
import java.util.Observable;
import java.util.Observer;
public class ForcastConditions implements Observer {
private float Temperature;
private float Pressure;
private float Humidity;
@Override
public void update(Observable o, Object arg) {
this.Temperature=((WeatherData.Data)arg).Temperature;
this.Pressure=((WeatherData.Data)arg).Pressure;
this.Humidity=((WeatherData.Data)arg).Humidity;
display();
}
public void display(){
System.out.println("*****公告板:******");
System.out.println("明日的温度:"+Temperature);
System.out.println("明日的气压:"+Pressure);
System.out.println("明日的湿度:"+Humidity);
}
}
InternetWeather.java:
package Java;
public class InternetWeather {
public static void main(String[] args){
CurrentCoditions currentCoditions;//公告板
ForcastConditions forcastConditions;//明日公告板
WeatherData weatherData;
weatherData=new WeatherData();
currentCoditions=new CurrentCoditions();
forcastConditions=new ForcastConditions();
//注册观察者
weatherData.addObserver(currentCoditions);
weatherData.addObserver(forcastConditions);
//注意顺序:通知顺序,后进先通知
//数据更新
weatherData.setData(30,150,40);
//取消今日公报版
weatherData.deleteObserver(currentCoditions);
weatherData.setData(37,250,80);
}
}
实现效果和自己定义接口一样。貌似Java中的内置观察者更加方便,但是Observable是继承,单继承自然有很多缺点。。。
总结:
观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为Observer,
Subject通知Observer变化。