目录
-
观察者模式
观察者模式(有时又被称为模型(Model)-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。
-
角色
1、抽象主题(Subject):
它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
2、具体主题(Concrete Subject):
将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。
3、抽象观察者(Observer):
为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
4、具体观察者(Concrete Observer):
实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。
-
案例
观察者模式是我们工作中最常用到的模式,也是最好从字面意思去理解的一个模式。我们生活中都会遇到这样一个场景,当我们有了小孩之后,会有一件麻烦事,小孩子一天中有大部分时间都在睡觉,而当他睡醒之后就得吃奶。那作为父亲,我们需要随时随地观察小孩有没有睡醒,醒来后立马冲奶粉喂奶。就这么一件事,我们用代码怎么解决呢?
刚参加工作的菜鸟程序员会这么写:
public class Main {
public static void main(String[] args) {
boolean cry = false;
while (!cry){
System.out.println("wait for child cry.......");
}
System.out.println("Child is crying.......");
}
}
哈哈,一个while循环,死死的盯住小孩,等小孩一醒立马跳出循环进行下一步操作。这应该是一段辣眼睛的代码,其他不说,这是典型的面向过程编程,不是面向对象。
等菜鸟程序员工作半年之后,终于懂了面向对象编程是怎么一回事,于是写出来如下代码:
public class Child {
private boolean cry;
public boolean isCry() {
return cry;
}
public void wakeUp() {
System.out.println("Child is crying.......");
cry = true;
}
}
public class Main {
public static void main(String[] args) {
Child child = new Child();
while (!child.isCry()){
System.out.println("wait for child cry.......");
}
System.out.println("Child is crying.......");
}
}
一段换汤不换药的代码,姑且认为他已经面向对象了吧。可是作为小孩的爸爸,有没有必要24小时盯着小孩,什么工作都不做,就等着喂奶呢?显然是没有必要的!
上面的观察是主动出击,做为观察者,被观察者走到哪里我跟到哪里,二十四小时形影不离。这种方式的观察显然浪费大量的资源。这时候我们需要化主动为被动,我们在被观察者身上安装一个监视器,有动静我们再行动也不迟。
于是菜鸟程序员在工作了一年之后,明白了这个道理,于是改进了之前的代码:
public class Dad {
public void food(){
System.out.println("小孩醒了,爸爸喂奶奶。。。。");
}
}
public class Child {
private boolean cry;
private Dad dad;
public Child() {
dad = new Dad();
}
public boolean isCry() {
return cry;
}
public void wakeUp() {
System.out.println("Child is crying.......");
cry = true;
dad.food();
}
}
public class Main {
public static void main(String[] args) {
Child child = new Child();
child.wakeUp();
}
}
化主动为被动,小孩醒后的哭声来通知爸爸喂奶,其余时间爸爸可以做其他工作。菜鸟虽然进步了,但还是菜鸟,这段代码显然耦合度太高,不利于扩展。试想一下,这时观察者不只一个怎么办,家了爸爸、妈妈和狗狗都想观察小孩,代码是不是会变成这样:
public class Child {
private boolean cry;
private Dad dad;
private Mum mum;
private Dog dog;
public Child() {
dad = new Dad();
mum = new Mum();
dog = new Dog();
}
public boolean isCry() {
return cry;
}
public void wakeUp() {
System.out.println("Child is crying.......");
cry = true;
dad.food();
mum.hug();
dog.wang();
}
}
工作超过三年的程序员,应该一眼就能看出上面代码的问题:现在观察者是三个,要是变成一百个怎么办?如果要方便扩展,代码应该这么写呢?三年以上经验的程序员会这么写:
public interface Observer {
void handleWakeEvent();
}
public class Dad implements Observer {
@Override
public void handleWakeEvent() {
System.out.println("小孩醒了,爸爸喂奶奶。。。。");
}
}
public class Mum implements Observer {
@Override
public void handleWakeEvent() {
System.out.println("小孩醒了,妈妈抱抱。。。。");
}
}
public class Dog implements Observer {
@Override
public void handleWakeEvent() {
System.out.println("小孩醒了,小狗汪汪。。。");
}
}
爸爸、妈妈和狗狗本质上拥有同样一种行为:观察宝宝睡觉。于是这样一种行为可以抽象成一个接口,爸爸、妈妈和狗狗再实现这个接口,于是他们成为了一个拥有观察行为的观察者。
对于小孩来说,我只通知拥有观察行为的观察者,于是可以这样写:
public class Child {
private boolean cry;
private List<Observer> observers = new ArrayList<>();
public Child() {
observers.add(new Dad());
observers.add(new Mum());
observers.add(new Dog());
}
public boolean isCry() {
return cry;
}
public void wakeUp() {
System.out.println("Child is crying.......");
cry = true;
for (Observer observer : observers) {
observer.handleWakeEvent();
}
}
}
感受到接口的好处了吗?这才是观察者模式,不管你三个还是一百个,统统给你通知到位,方便扩展。
当然有时候被观察者在通知观察者的时候,是需要携带一些信息的,也就是传参数。这时候我们要新增一个用于传递参数的事件类,把事件源(小孩)和时间发生时间传递给观察者:
public class WakeUpEvent {
private long time;
private Child child;
public WakeUpEvent(long time, Child child) {
this.time = time;
this.child = child;
}
public long getTime() {
return time;
}
public Child getChild() {
return child;
}
}
同时改变观察者和被观察者的写法:
public interface Observer {
void handleWakeEvent(WakeUpEvent event);
}
public class Child {
private boolean cry;
private List<Observer> observers = new ArrayList<>();
public Child() {
observers.add(new Dad());
observers.add(new Mum());
observers.add(new Dog());
}
public boolean isCry() {
return cry;
}
public void wakeUp() {
System.out.println("Child is crying.......");
cry = true;
WakeUpEvent event = new WakeUpEvent(System.currentTimeMillis(), this);
for (Observer observer : observers) {
observer.handleWakeEvent(event);
}
}
}
这样就实现了被观察者和观察者之间传递参数了,这时,程序场上的老鸟们肯定会发现一个问题,被观察者与事件之间产生了耦合,WakeUpEvent里面关联了Child。这也减低了程序的扩展性,假如观察者更换了观察对象,WakeUpEvent岂不是就不能用了吗?这时老鸟们站出来润色润色,新增一个接口:
public interface Subject {
}
public class WakeUpEvent {
private long time;
private Subject source;
public WakeUpEvent(long time, Subject source) {
this.time = time;
this.source = source;
}
public long getTime() {
return time;
}
public Subject getSource() {
return source;
}
}
public class Child implements Subject {
private boolean cry;
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void wakeUp() {
System.out.println("Child is crying.......");
cry = true;
WakeUpEvent event = new WakeUpEvent(System.currentTimeMillis(), this);
for (Observer observer : observers) {
observer.handleWakeEvent(event);
}
}
}
看出老鸟们的高明之处了吗?看似增加了一个无意义的接口,但却在谈笑间化解了WakeUpEvent和Child之间的耦合。这时WakeUpEvent不光可以用于观察小孩睡觉,也可用于观察爷爷奶奶睡觉。
到此为止,观察者模式终于理解清楚了,也完成了从菜鸟到老鸟的进化。程序场上的菜鸟们终于明白老鸟的十年工作经营可不是白混的。
-
总结
观察者模式是工作中最最常用的模式,Android中的触摸事件、按键事件、点击事件等等,无一不是通过观察者模式来实现的。所以充分理解观察者模式有利于我们从程序场菜鸟到老鸟的转变。