2019独角兽企业重金招聘Python工程师标准>>>
1. 是什么——定义
定义对象间的一种一对多的依赖关系,让多个观察者同时监听某一个主题现象,当一个对象的状态发生改变时,会通知所有观察者对象,所有依赖于它的对象都得到通知并被自动更新。
2. 为什么——特点
一个对象状态改变的同时,需要同时改变其他对象。
3. 什么时候用——适用性
l 一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
l 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
l 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
l 一个对象必须通知其他对象,而并不知道这些对象是谁。
l 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
4. UML图
5. 怎么用——使用方法
需求:
气象局发布天气预报,学生和上班族根据天气做出相应反应。
5.1 气象局
设计WeatherStation:
内部维护一个{“晴天”, “下雨”, “雾霾”, “冰雹”, “狂风”, “暴雪”}
气象局要根据观测进行天气更新
updateWeather();
气象局要不断工作,不断观测天气:
死循环中每隔1-2秒执行一次
updateWeather() {
while(true){
//更新天气
}
}
5.2 学生
学生要设计看天气预报的方法
notifyWeather();
注意这里可以给气象局的weather只设置get方法,这样别人只能看
让学生每次都去看气象局的天气,也就是在notify方法中传入天气
这里使用简单if,else-if,else结构:
l 晴天——开心上学
l 下雨——打着雨伞上学
l 雾霾——开心的吸着毒上学
l 冰雹——学校门口砸了个大坑,不去上学
l 狂风——学校被吹走了,不去上学
l 暴雪——学校门口被雪埋了,不去上学
public void notifyWeather(String weather) {
if ("晴天".equals(weather)) {
System.out.println(name + "开开心心的上学!");
}else if ("下雨".equals(weather)) {
System.out.println(name + "打伞上学!");
}else if ("雾霾".equals(weather)) {
System.out.println(name + "开心的吸着毒上学!");
}else if ("冰雹".equals(weather)) {
System.out.println("学校门口被砸了个坑,不去了!");
}else if ("下雪".equals(weather)) {
System.out.println("学校门口被雪埋了,不去上学!");
}else if ("狂风".equals(weather)) {
System.out.println("学校被吹走了,没学上了!");
}
}
之后,在Client中编写:
while(true){
student.notifyWeather(station.getWeather());
}
发现这种方法实在太差,这样学生啥也别干了,光盯着天气预报算了。
而且,如果只是这样,会在客户端留下一个非常严重的问题:
因为气象局一直在观测天气,所以work()方法后面的代码永远无法执行。
5.3 解决代码无法执行的bug
new Thread(new Runnable() {
@Override
public void run() {
while(true){
updateWeather();
int s = random.nextInt(1000)+1000;
try {
Thread.sleep(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
运行之后发现会出现观察时间与气象局更新时间不匹配的bug。
问题:现在是人主动去关注天气,能不能让气象局播天气预报?
5.4 气象局维护学生
新建一个方法:
addStudent();
每次更新天气时,迭代学生列表,通知学生
public void updateWeather() throws Exception {
while (true) {
weather = weathers[random.nextInt(weathers.length)];
System.out.println("当前的天气是:" + weather);
for (Student student : students) {
student.notifyWeather(weather);
}
Thread.sleep(random.nextInt(1000) + 1000);
}
}
到此为止,已经构成了观察者模式
5.5 新增上班族
l 晴天——开心上班
l 下雨——打着雨伞上班
l 雾霾——带着消毒面具上班
l 冰雹——带着三级头盔上班
l 狂风——拖着大石头上班
l 暴雪——披着被子上班
public void notifyWeather(String weather) {
if ("晴天".equals(weather)) {
System.out.println(name + "开开心心的上班!");
}else if ("下雨".equals(weather)) {
System.out.println(name + "打伞上班!");
}else if ("雾霾".equals(weather)) {
System.out.println(name + "戴着防毒面具上班!");
}else if ("冰雹".equals(weather)) {
System.out.println(name + "戴着三级头去上班");
}else if ("下雪".equals(weather)) {
System.out.println(name + "披着被子去上班");
}else if ("狂风".equals(weather)) {
System.out.println(name + "拖着大石头上班!");
}
}
5.6 气象局维护上班族
试着在Client中创建Employee的对象。。。
发现在Client中不能addEmployee!
观察上班族和学生,他们都有一个共同的特点:
看天气预报!根据天气不同,作出不同的反应!
这属于行为——可以抽出一个接口(面向接口编程而不是实现类)
5.7 抽取被通知者
public interface Observable {
public void notifyWeather(String weather);
}
之后,在气象局中使用多态即可。
5.8 新增天气预报软件,抽取天气通知类型(观察者)
无需多言,与上面的思路完全一致。
6. 实际应用
其实,Java的GUI实现的组件事件监听,本质就是观察者模式。
----当某个按钮被点击时,要通知另一些组件作出反应(如点击全选,所有复选框被选中)
但发现:GUI的事件监听机制是使用匿名内部类!
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("我被点击了。。。");
}
});
这种设计思路是:只要被通知的对象符合一定的特征即可!
这不就是匿名内部类的使用思路吗???(突然滑稽)
7. Java中的观察者模式支持
翻开java.util包,可以发现一个类和一个接口:
Observer类,Observable接口
实现Observable接口的,都是被通知对象。
Observer本身就可以通知,当然可以继承Observer类,扩展功能。。。
注--- 使用jdk自带的观察者模式的缺点:
- Observable是一个类,而不是一个接口,导致Observable类的扩展性不高,不如自己实现的观察者模式灵活
- Observable将某些方法保护了起来(setChanged()和clearChanged()为protected),这意味着除非继承自Observable,否则将有关键的方法不能调用。导致无法通过组合的方式使其它类获得Observable类的功能