设计模式学习-观察者模式

1.定义

定义对象间一种一对多的依赖关系,使得当每一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。

2.类图

3.代码示例

我们定义一个场景:热水壶在烧开水,小孩和妈妈都关注烧开水的过程,各自有其处理方法。用while死循环一直轮询虽然可以实现这样的场景,但性能上让人无法接受。

为方便大家copy源码放在本机测试,我将代码写进一个java类中,其它参与类都是非public的。

  1 package com.zhaoyangwoo.observer;
  2 
  3 import java.util.ArrayList;
  4 import java.util.List;
  5 
  6 /**
  7  * Created by john on 16/8/3.
  8  */
  9 //定义被观察者接口
 10 interface Subject {
 11 
 12     void attachObv(Observer o);
 13 
 14     void detachObv(Observer o);
 15 
 16     void notifyObvs();
 17 }
 18 
 19 //定义观察者接口
 20 interface Observer {
 21     void update(Subject subject);
 22 }
 23 
 24 //定义具体被观察者
 25 public class WaterHeater implements Subject {
 26 
 27     //存储观察者
 28     private static List<Observer> observerList = new ArrayList<>();
 29 
 30     public int getTemperature() {
 31         return temperature;
 32     }
 33 
 34     public void setTemperature(int temperature) {
 35         this.temperature = temperature;
 36     }
 37 
 38     private int temperature = 0;
 39 
 40     @Override
 41     public void attachObv(Observer o) {
 42         observerList.add(o);
 43     }
 44 
 45     @Override
 46     public void detachObv(Observer o) {
 47         observerList.remove(o);
 48 
 49     }
 50 
 51     @Override
 52     public void notifyObvs() {
 53         observerList.forEach(r -> r.update(this));
 54     }
 55 
 56     //模拟烧开水的过程
 57     public void heat() {
 58         temperature = 0;
 59         for (int i = 0; i < 100; i++) {
 60             temperature++;
 61             this.notifyObvs();
 62             try {
 63                 //停100ms
 64                 Thread.sleep(100);
 65             } catch (InterruptedException e) {
 66                 e.printStackTrace();
 67             }
 68         }
 69     }
 70 
 71 
 72     //场景类
 73     public static void main(String[] args) {
 74         WaterHeater waterHeater = new WaterHeater();
 75         Mother mother = new Mother();
 76         Child child = new Child();
 77         waterHeater.attachObv(mother);
 78         waterHeater.attachObv(child);
 79         waterHeater.heat();
 80         waterHeater.detachObv(child);
 81         waterHeater.heat();
 82         System.out.println("烧水结束");
 83     }
 84 }
 85 
 86 //具体观察者
 87 class Child implements Observer {
 88 
 89     @Override
 90     public void update(Subject subject) {
 91         WaterHeater waterHeater = (WaterHeater) subject;
 92         System.out.println("现在的水温是" + waterHeater.getTemperature() + "度,人家还是个宝宝,跟我没关系");
 93     }
 94 }
 95 
 96 //具体观察者
 97 class Mother implements Observer {
 98 
 99     @Override
100     public void update(Subject subject) {
101         WaterHeater waterHeater = (WaterHeater) subject;
102         if (waterHeater.getTemperature() > 99) {
103             System.out.println("现在的水温是" + waterHeater.getTemperature() + "度,水烧开了,我要把水装进热水瓶");
104         }
105     }
106 }

 

4.应用场景举例

  • 一个类的状态变化需要通知其他类知晓,并且这些其他类是可以动态配置的
  • 可以实现订阅/发布模型

5.JDK源码中的模式实现

JDK原生对观察者模式支持。通过java.util.Observable和java.util.Observer两个类,很容易实现观察者模式。通过阅读其源码,可以发现原理都是一样的。当然源码里的实现是线程安全的。我们用这两个类重写我们的场景:

 1 // jdk版本的被观察者,不需要自己实现调用通知/注册之类的操作
 2 class WaterHeaterJava extends Observable {
 3     public int getTemperature() {
 4         return temperature;
 5     }
 6 
 7     public void setTemperature(int temperature) {
 8         this.temperature = temperature;
 9     }
10 
11     private int temperature = 0;
12 
13     public void heat() {
14         temperature = 0;
15         for (int i = 0; i < 100; i++) {
16             temperature++;
17             //这里一定要注意,如果要notifyObservers生效,一定要调用setChanged告知已经发生了change,可以通知观察者了.否则notifyObservers不工作
18             super.setChanged();
19             super.notifyObservers();
20             try {
21                 Thread.sleep(100);
22             } catch (InterruptedException e) {
23                 e.printStackTrace();
24             }
25         }
26     }
27 
28     //场景类
29     public static void main(String[] args) {
30         WaterHeaterJava waterHeater = new WaterHeaterJava();
31         MotherJava mother = new MotherJava();
32         ChildJava child = new ChildJava();
33         waterHeater.addObserver(mother);
34         waterHeater.addObserver(child);
35         waterHeater.heat();
36         waterHeater.deleteObserver(child);
37         waterHeater.heat();
38         System.out.println("Java版烧水结束");
39     }
40 }
41 
42 class ChildJava implements java.util.Observer {
43 
44     @Override
45     public void update(Observable o, Object arg) {
46         WaterHeaterJava waterHeater = (WaterHeaterJava) o;
47         System.out.println("现在的水温是" + waterHeater.getTemperature() + "度,人家还是个宝宝,跟我没关系");
48     }
49 }
50 
51 class MotherJava implements java.util.Observer {
52 
53     @Override
54     public void update(Observable o, Object arg) {
55         WaterHeaterJava waterHeater = (WaterHeaterJava) o;
56         if (waterHeater.getTemperature() > 99) {
57             System.out.println("现在的水温是" + waterHeater.getTemperature() + "度,水烧开了,我要把水装进热水瓶");
58         }
59     }
60 }
View Code

关于setChanged,看看它的源码就明白了

 1 public void notifyObservers(Object arg) {
 2         /*
 3          * a temporary array buffer, used as a snapshot of the state of
 4          * current Observers.
 5          */
 6         Object[] arrLocal;
 7 
 8         synchronized (this) {
 9             /* We don't want the Observer doing callbacks into
10              * arbitrary code while holding its own Monitor.
11              * The code where we extract each Observable from
12              * the Vector and store the state of the Observer
13              * needs synchronization, but notifying observers
14              * does not (should not).  The worst result of any
15              * potential race-condition here is that:
16              * 1) a newly-added Observer will miss a
17              *   notification in progress
18              * 2) a recently unregistered Observer will be
19              *   wrongly notified when it doesn't care
20              */
21 
22             //看这里
23             if (!changed)
24                 return;
25             arrLocal = obs.toArray();
26             clearChanged();
27         }
28 
29         for (int i = arrLocal.length-1; i>=0; i--)
30             ((Observer)arrLocal[i]).update(this, arg);
31     }    
View Code

 

6.思考

思考如下两个问题

  • 性能问题 

  如果注册的观察者较多,或者观察者处理能力弱、耗时长,那么很可能出现性能问题,毕竟只是简单的遍历调用。

  • 订阅/发布

  当然,对于订阅/发布模型的支持有更好的开源框架,各种mq实现了这种模型,并且是异步架构,性能也有保障。 

  • 推或拉

  大家都喜欢说推模式和拉模式。其实本质上来说都是推模式,这也是使用观察者模式带来的好处。拉模式只不过推了一个引用,可以通过这个引用拿到更多的信息而已。

7.参考

1.《JAVA与模式》之观察者模式
 

posted @ 2016-08-04 10:27  jiudianban  阅读(636)  评论(0编辑  收藏  举报