该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
观察者(Observer)模式:
让你的对象知悉现状——不错过感兴趣的事;
观察者模式是JDK中使用最多的模式之一;
我们还会介绍1对多关系及松耦合;
有了观察者,你将消息灵通;
气象观测站:
WeatherData对象从气象观测站获取天气状况(温度,湿度,气压);
有三种布告板(日后会向外部公布接口,由其他人扩展新的布告板),显示不同要求的天气信息;
当WeatherData对象获得最新的测量数据时,三种布告板必须实时更新;
Weather对象知道如何与气象站联系,获得相关的数据,这个我们不关注;
我们需要建立一个应用,利用WeatherData对象取得的数据,更新诸多布告板的显示;
一个WeatherData类:
获取气象数据(温度,湿度,气压):getTemperature();getHumidity();getPressure();
回调方法:measurementsChanged(); 当气象测量更新,此方法会被调用;
我们知道的:
WeatherData类具有getter方法,获取气象参数;
当新的气象数据更新时,measurementsChanged()方法会被调用;随机布告板的显示也需要更新;
此系统需要可扩展;(添加或删除布告板)
一种实现方式:
我们可以在WeatherData类中,新建三个实例变量,存储当前的三个布告板实例;
每个布告板实例都有一个update方法,可以将传入的气象参数(温度,湿度,气压)进行显示更新;
当measurementsChanged()方法被调用的时候,WeatherData调用getter方法获取气象参数,诸多布告板实例变量调用各自的update方法;
问题出在哪:
上边是一种针对具体实现编程的方式,会导致日后增加或删除布告板的时候,修改程序;
在这里我们看到了统一的接口:布告板的update()方法,参数可以是温度、湿度、气压等气象数据;
改变的不同布告板的实例变量,按照我们已经学习到的原则,我们需要将改变的地方,封装起来;
认识观察者模式:
以报纸和杂志的订阅为例:
报社负责出版报纸;
向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来;当不想再看报纸的时候,取消订阅;
只要报社还在,就会一直有人(或单位)订阅或取消订阅报纸;
出版者+订阅者=观察者模式:
出版者:主题(Subject);
订阅者:观察者(Observer);
主题对象:管理某些数据,当这些数据改变的时候,就会通知观察者;
观察者:订阅(注册)主题 以便在主题数据改变的时候,能够收到更新;
观察者还可以取消订阅,主题收到取消订阅的请求后,就会把该观察者除名;对象也可以重新订阅,再次成为观察者;
定义观察者模式:
观察者模式:
定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,他的所有依赖者都会收到通知并自动更新;
观察者模式定义了一系列对象之间的一对多关系;
当一个对象改变的时候,其他依赖者都会收到通知;
主题和观察者定义了一对多的关系,观察者依赖于此主题,只要主题状态一有改变,观察者就会被通知;
观察者模式的类图:
主题接口:主题对象实现,注册观察者、从观察者中删除、通知观察者等;
每个主题可以有很多观察者;
观察者接口:观察者对象实现,更新方法,主题状态改变的时候会被调用;
观察者使用主题的状态,依赖主题来告诉他们这些状态何时改变了;这比很对对象控制同一份数据更容易得到干净的OO设计;
松耦合:
观察者模式提供了一种对象设计,让主题和观察者之间松耦合;
因为,主题只知道观察者实现的接口(interface-Observe),并不需要知道观察者具体类是谁,实现了那些细节;
且,任何时候都可以增加新的观察者,主题依赖的实现Observer接口的对象列表;
主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象;
我们可以独立的复用主题或观察者,二者并非紧耦合;改变观察者或主题一方也不会影响另一方,因为二者松耦合;
设计原则:
为了交互对象之间的松耦合设计而努力;
松耦合能使对象之间的依赖降到最低;
气象站:
设计气象站:
每个布告板都有差异,所以需要使用同一个接口;
布告板可以实现观察者接口统一update方法,还可以实现DisplayElement,统一展示信息的方法;
每个布告板都应该有一个subject指针指向WeatherData对象,方便订阅和取消订阅;
实现气象站:
新建接口;
WeatherData实现主体接口;
建立布告板;
运行测试类;
启动气象站:
bogon:第二章 观察者模式 huaqiang$ javac *.java
bogon:第二章 观察者模式 huaqiang$ java WeatherDataDrive
LookGround1Display——
Conditions:
Temperature:5.2
Humidity:5.6
LookGround2Display——
Conditions:
Temperature:5.2
Humidity:5.6
Pressure13.4
LookGround2Display——
Conditions:
Temperature:5.2
Humidity:5.6
Pressure13.4
对于启动的代码,可以使用具体的气象站类型,布告板则使用接口类型:
public class WeatherDataDrive{
public static void main(String[] args){
//模拟气象变化
WeatherData station = new WeatherData();
Observer display1 = new LookGround1Display(station);
Observer display2 = new LookGround2Display(station);
//模拟信息更新
station.setMeasurements((float)5.2,(float)5.6,(float)13.4);
station.removeObserver(display1);
//模拟信息更新
station.setMeasurements((float)5.2,(float)5.6,(float)13.4);
}
}
通过上述示例,我们可以注册观察者、取消观察者、发送通知给观察者;
现在,如果有人实现了一个新的布告板,并注册为气象站的观察者,则在气象信息变化的时候就会收到通知;
除了自行实现观察者模式,Java也有内置的Observer模式;
Java内置的观察者模式:
java.util包 内包含基本的Observable类和Observer接口,和我们自行实现的很相似,使用上会更方便;
查看Java文档表述如下图;
通过文档的说明:
我们的主题需要扩展自Observable类(注意这是一个继承关系),被称为“可观察者”类;
我们的观察者需要实现Observer接口,然后调用任何Observable对象的addObserver方法(不再当的话,调用deleteObserver方法即可);
可观察者要如何送出通知:
先调用setChanged()方法,标记状态已经改变的事实;
然后调用两种notifyObservers()方法中的一个(notifyObservers()/notifyObservers(Object arg)),将数据“推”出去;
观察者如何接收通知:
实现更新方法(注意方法签名);
如果调用notifyObservers()之前没有事先调用setChanged(),观察者就不会被通知;
setChanged()方法,可以让你在更新观察者时,有更多的弹性;
比如对于气象数据,当气温+-1度的时候,才认为有更新的必要性,就可以在这里做一下限制;
clearChanged()方法将changed状态设置回false;
另一个hasChanged()方法,会告诉changed标志的当前状态;
利用内置的支持重做气象站:
import java.util.Observer;
import iava.util.Observable;
WeatherData extends Observable{}
WeatherData的构造器方法也不再需要为记住观察者而建立数据结构了;
对于注册、删除、通知观察者的方法也都是用父类的方法即可;
LookGroundDisplay implements Observer, DisplayElement{}
实现的update(Observable obs, Object arg)方法中,arg参数可以传各种数据,你也可已经WeatherData对象传回来,使用其状态的getter方法“拉”数据;
值得注意的是:
不要依赖于观察者被通知的次序;
同时,按照之前的设计原则,内置的Observable是一个类,这并不是一件好事:
因为这是一个类,设计必须继承它,但如果某类向同时具有Observable类和另一个超类的行为,就比较麻烦;
在文档中,我们注意到,setChanged()方法是受保护的(protected),这意味着,除非使用继承,否则无法创建示例并组合到自己的对象中来;我们已经知道应该“多用组合,少用继承”;
如果使用内置的观察者模式不方便,那就自行实现,重要的是,你已经理解了观察者模式;
在JDK中,观察者模式的应用举例:
JButton:
观察一下JButton的超类AbstractButton,会看到许多增加与删除倾听者(listener)的方法;
这些方法让观察者感应到Swing组件的不同事件类型,比如ActionListener能监听到可能发生在按钮上的动作;
添加观察者:
ActionListener接口定义的方法:
示例(源码):
我们在JFrame上放了一个按钮;
制造了了两个倾听者(观察者),观察者的类定义,使用了内部类;
发生操作的时候 会调用actionPerformed()方法;
import javax.swing.JFrame;
import java.awt.Panel;
import javax.swing.JButton;
import java.awt.Dimension;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.lang.Object;
public class SwingObserverExample{
JFrame frame;
public static void main(String[] args){
SwingObserverExample example = new SwingObserverExample();
example.go();
}
public void go(){
frame = new JFrame();
frame.setBounds(0,0, 400, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Panel p = new Panel();
JButton button = new JButton("Should I do it?");
button.addActionListener(new AngelListener());
button.addActionListener(new DevilListener());
Dimension preferredSize = new Dimension(200,200);
button.setPreferredSize(preferredSize);
p.add(button);
frame.add(p);
frame.setVisible(true);
}
class AngelListener implements ActionListener{
public void actionPerformed(ActionEvent event){
System.out.println("Don`t do it.");
}
}
class DevilListener implements ActionListener{
public void actionPerformed(ActionEvent event){
System.out.println("Come on,do it.");
}
}
}
程序运行截图:
总结:
1.观察者模式定义了对象之间一对多的关系;
2.主题(可观察者)用一个共同的接口来更新观察者;
3.观察者和可观察者之间用松耦合方式结合(loosecoupling);
可观察者不知道观察者的细节,只知道观察者实现了观察者接口;
4.使用此模式时,不可以依赖特定的通知次序;
5.Java提供了观察者模式的实现java.util.Observable;
6.有必要的话,可以自己实现自己的观察者模式;
OO基础:
抽象;
OO原则:
封装变化
多用组合,少用继承
针对接口编程,不针对实现编程
——为交互对象之间的松耦合设计而努力;
OO模式:
——观察者模式:在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新;