前言
观察者(Observer
)模式是一种使用频率很高的设计模式,之前在介绍Javax.Servlet
包下的Listener
的时候,我们知道Listener
能够在某一特定事件发生的时候(比如Servlet容器
的创建和销毁)作出响应。为了完成这个功能,Listener
就采用了观察者模式。
场景
假设我们现在有一个闹钟类Clock
,当把它开启的时候它就会在一个特定的时间闹铃,如果类Son
听到这个闹钟的铃声,就会起床去上学。为了模拟这个场景,我们可以简单的写下如下代码。
-
Clock
和Son
类
class Clock {
private volatile boolean ring;
public void turnOn() {
System.out.println("闹钟响了");
ring = true;
}
public boolean isRing(){
return ring;
}
}
class Son {
public void goToSchool(){
System.out.println("孩子起床上学");
}
}
- 测试类
public class ObserverTest {
public static Clock clock = new Clock();
public static void main(String[] args) {
Son son = new Son();
new Thread(()->{
try{
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
clock.turnOn();
}).start();
while (!clock.isRing()) {
// wait
}
son.goToSchool();
}
}
- 实验结果
闹钟响了
孩子起床上学
Process finished with exit code 0
这个实现方式很简单,Son
一直轮询判断闹钟有没有响就可以了。缺点也很显然,沦陷的时候一直在空转,占用cpu
时间,造成了资源浪费。那有什么办法可以不用空转去轮询状态呢?我们可以让闹钟闹铃的时候直接通知Son
起来处理就好了,也就是让son
监听clock
转换为clock
通知son
。
- 优化-取消轮询
class Clock {
private volatile boolean ring;
private Son son = new Son();
public void turnOn() {
System.out.println("闹钟响了");
ring = true;
son.goToSchool();
}
public boolean isRing(){
return ring;
}
}
现在我们把son
捆绑在clock
上,当clock
的turn on
方法一调用就立刻调用son
的goToSchool()
方法。现在可以说我们把son
注册进了clock
,使其成为clock
的观察者。观察者模式到这里就结束了吗? 显然没有,考虑到我们对于一个事件并不是只有一个观察者,假设在这个场景下,闹钟一响,不仅儿子要起床上学,父亲也要起床上班,母亲则需要出门买菜,也就是说对于闹钟闹铃
这个事件,有3
个观察者,则上述代码要改成如下。
- 添加的
Father
,Mather
类
class Father {
public void goToWork(){
System.out.println("爸爸起床上班");
}
}
class Mother {
public void goBuyFood(){
System.out.println("妈妈出门买菜");
}
}
class Son {
public void goToSchool(){
System.out.println("孩子起床上学");
}
}
-
Clock
类
class Clock {
private volatile boolean ring;
private Son son = new Son();
private Father father = new Father();
private Mother mother = new Mother();
public void turnOn() {
System.out.println("闹钟响了");
ring = true;
son.goToSchool();
father.goToWork();
mother.goBuyFood();
}
public boolean isRing(){
return ring;
}
}
添加了Father
、Mother
类之后,我们还要把它们写死进Clock
代码里,此外还要明确每个观察者需要被执行的方法。所有的观察者和Clock
这个被观察对象紧紧的耦合在一起了。代码的扩展性特别差。何如修改才能让其真正成为观察者模式呢?我们知道约定优于配置,既然我们不知道每一个观察者需要被调用的具体方法,那我们就约定一个方法叫做doSomething()
,所有的观察者只要实现某个接口实现该方法就可以了,此外有了这个接口,我们可以将所有的观察者对象看成一类,在被观察对象类中提供一个注册接口就可以了。让我们按照这个思路来进行优化。
- 约定
Observer
接口
interface Observer {
public void doSomething();
}
-
Son
、Mother
、Father
类都来实现这个接口
class Father implements Observer{
public void goToWork(){
System.out.println("爸爸起床上班");
}
@Override
public void doSomething() {
this.goToWork();
}
}
class Mother implements Observer {
public void goBuyFood(){
System.out.println("妈妈出门买菜");
}
@Override
public void doSomething() {
goBuyFood();
}
}
class Son implements Observer{
public void goToSchool(){
System.out.println("孩子起床上学");
}
@Override
public void doSomething() {
}
}
- 修改
Clock
类提供添加观察者方法
class Clock {
private volatile boolean ring;
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer){
observers.add(observer);
}
public void turnOn() {
System.out.println("闹钟响了");
ring = true;
for (Observer observer : observers) {
observer.doSomething();
}
}
public boolean isRing(){
return ring;
}
}
- 进行测试
public class ObserverTest {
public static Clock clock = new Clock();
public static void main(String[] args) {
clock.addObserver(new Son());
clock.addObserver(new Father());
clock.addObserver(new Mother());
clock.turnOn();
}
}
- 输出结果
闹钟响了
爸爸起床上班
妈妈出门买菜