《观察者模式》
观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
观察者模式定义了一种一对多的依赖关系,让多个观察者同时监听某一个主题对象。当这个主题对象的状态发生变化时,就会通知所有的观察者,使他们更新自己。
1.观察者模式的结构
抽象主题角色(Subject):抽象主题将所有的观察者保存在一个集合中,并定义了增加、删除、通知三个方法。
具体主题角色(ConcreteSubject):当自己的状态发生变化时,就会通知所有登记过的观察者。
抽象观察者角色(Observer):定义了一个更新的方法,让具体主题对象在通知时会调用该方法。
具体观察者角色(ConcreteObserver):存储和主体相同的状态,当被通知时,具体的update方法被调用,状态得以更新。
2.自定义观察者模式实现HeadFirst设计模式中的气象站的例子:
//通知者接口(Subject)
public interface Subject {
//添加观察者
void addObserver(Observer obj);
//移除观察者
void removeObserver(Observer obj);
//通知观察者
void notifyObservers();
}
//主题类(ConcreteSubject)
public class WeatherData implements Subject {
private List<Observer> list;
private float temp;
private float humidity;
private float pressure;
//初始化集合
public WeatherData() {
list = new ArrayList<Observer>();
}
//添加观察者
@Override
public void addObserver(Observer obj) {
list.add(obj);
}
//移除观察者
@Override
public void removeObserver(Observer obj) {
list.remove(list.indexOf(obj));
}
//通知所有观察者,会调用所有观察者的update()
@Override
public void notifyObservers() {
for (Observer obj : list) {
obj.update(temp, humidity, pressure);
}
}
//一旦气象站监测的数据发生变化,就会调用该方法
public void mesureChanged(){
notifyObservers();
}
//该方法用来模拟监测数据变化,会调用mesureChanged方法
public void setMeasurements(float temp,float humidity,float pressure){
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
mesureChanged();
}
}
//抽象观察者(Observer)
public interface Observer {
void update(float temp,float humidity,float pressure);
}
//展示的公告(ConcreteObserver)
public class CurrentConditionsDisplay implements Observer{
private float temp;
private float humidity;
private float pressure;
//直接在构造函数中完成观察者的添加
public CurrentConditionsDisplay(Subject subject) {
subject.addObserver(this);
}
//展示数据
public void display() {
System.out.println("温度为"+temp);
System.out.println("湿度为"+humidity);
System.out.println("压力为"+pressure);
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
display();
}
}
//测试
public class TestObserver {
@Test
public void testObserver(){
//创建通知者
WeatherData wd = new WeatherData();
//创建观察者
CurrentConditionsDisplay ccd = new CurrentConditionsDisplay(wd);
//模拟测试温度变化
wd.setMeasurements(20, 1l, 22);
}
}
3.观察者模式中的推模型和拉模型
推模型:就像上面的示例,如果气象站监测的数据发生变化,它会把所有的数据全部发送给观察者, 不管该观察者是否需要所有的数据。
拉模型:主题在通知观察者时,一般会将自身对象通过update()方法传递过去,然后自身提供数据的getter方法,让观察者通过自身需要通过getter()方法获取想要的数据。
通过Java.util.Observable类和Java.util.Observer接口可以Java内置的观察者模式,我们通过Java内置的观察者模式将上面的示例改造成拉模型:
//继承Jdk内置通知者
public class WeatherData extends Observable {
private float temp;
private float humidity;
private float pressure;
//一旦气象站监测的数据发生变化,就会调用该方法
public void mesureChanged(){
//调用通知方法前,必须调用setChanged()
setChanged();
notifyObservers();
}
//该方法用来模拟监测数据变化,会调用mesureChanged方法
public void setMeasurements(float temp,float humidity,float pressure){
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
mesureChanged();
}
//听过getter方法供观察者获取数据
public float getTemp() {
return temp;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
//实现Java内置的Observer接口
public class CurrentConditionsDisplay implements Observer {
private Observable obs;
private float temp;
private float humidity;
private float pressure;
//传入通知者,用于添加观察者
public CurrentConditionsDisplay(Observable obs) {
obs.addObserver(this);
this.obs = obs;
}
@Override
public void update(Observable obs, Object obj) {
if (obs instanceof WeatherData){
//通过getter方法来获取数据
WeatherData wd = (WeatherData) obs;
this.temp = wd.getTemp();
this.humidity = wd.getHumidity();
this.pressure = wd.getPressure();
}
display();
}
public void display(){
System.out.println("温度为"+temp);
System.out.println("湿度为"+humidity);
System.out.println("压力为"+pressure);
}
}
//测试
public class TestObserver {
@Test
public void testObserver(){
//创建主题
WeatherData wd = new WeatherData();
//创建观察者
CurrentConditionsDisplay ccd = new CurrentConditionsDisplay(wd);
//更新主题数据
wd.setMeasurements(10, 12, 13);
}
}
尽管Java为我们提供了内置的观察者模式,但是其中也有不足之处。我们发现 Observable是一个类而不是接口,所以我们要实现观察者模式就必须继承它,而由于Java并不支持多继承,所以我们的主题类就没办法继承别的类。而且由于Observable的setChanged方法被protected修饰,导致我们没法通过组合的方式调用该方法,这也违法了Java的设计原则“多组合,少继承"。
4.推模型和拉模型的区别
1)推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。
2)推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
5.总结
优点:
1.Java中有很多地方都是用了观察者模式,比如Swing、RMI等,观察者模式在被观察者和观察者之间建立了一种抽象的耦合,即被观察者并不知道观察者具体的实现,只知道它们都实现了同一个就,这样可以很好的实现程序见的松耦合。
缺点:
1.如果使用Java内置的观察者模式,应该注意它存在的一些问题,比如在有多个观察者时,不要依赖特定的通知顺序,Observable是个类且没有实现任何借口,导致扩展性不好。
2.如果被观察者有过多的直接观察者或间接观察者,这就会导致每一次通知的耗时过长。