观察者模式 Observer的定义

  观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。

  这个主题对象在状态上发生变化时,会通知所有观察者对象,让它们能够自动更新自己。

第一部分

这里有一个例子,是马士兵老师在讲解观察者模式的时候给出的例子,个人认为对理解观察者模式有很大的用处,自己查到的一些博文也写得很好,但是太过于一板一眼了,不便于去理解。具体的例子是这样的:一个小孩在睡觉,当小孩醒过来之后,爸爸要feed,爷爷要哄哄抱抱,小狗汪汪叫。在这里这个睡觉的小孩就是被观察的对象,后面三个对象就是观察者,小孩的状态发生改变的时候,就相当于一个事件被触发了,观察者(或者应该叫做监听者)会做出相应的动作。下面是具体的是代码实现。

第一步:我们定义被观察对象

1 class Child implements Runnable {
 2     //用List来存放不同的监听
 3     private List<WakeUpListener> wakeUpListeners = new ArrayList<WakeUpListener>();
 4 
 5     //List中添加监听的操作
 6     public void addWakenUpListener(WakeUpListener l) {
 7         wakeUpListeners.add(l);
 8     }
 9 
10     //被观察者小孩的状态发生改变,则会通知观察者,使其执行各自的performAction方法
11     public void wakeUp() {
12         for (int i = 0; i < wakeUpListeners.size(); i++) {
13             WakeUpListener l = wakeUpListeners.get(i);
14             //
15             l.performAction(new WakeUpEvent(System.currentTimeMillis(), "沙发上",
16                     this));
17         }
18     }
19 
20     //监听线程的run()
21     public void run() {
22         try {
23             Thread.sleep(1000);
24         } catch (InterruptedException e) {
25             e.printStackTrace();
26         }
27         this.wakeUp();
28     }
29 
30 }

第二步:给出监听接口,具体的观察者都去实现这个接口,具体的观察者复写接口的performAction方法,小孩的状态发生变化,做出响应

1 interface WakeUpListener {
2     public void performAction(WakeUpEvent wakeUpEvent);
3 }

第三步:定义具体的观察者

1 /*
 2  * 具体观察者ConcreteObserver,被观察的对象child的状态发生变化这一事件会触发观察者的performAction方法,针对被观察者的变化做出反应
 3  */
 4 //具体观察者一
 5 class Dad implements WakeUpListener {
 6 
 7     public void performAction(WakeUpEvent wakeUpEvent) {
 8         System.out.println("..feed..");
 9     }
10 
11 }
12 //具体观察者二
13 class Dog implements WakeUpListener {
14 
15     public void performAction(WakeUpEvent wakeUpEvent) {
16         System.out.println("..汪汪..");
17     }
18 
19 }
20 //具体观察者三
21 class Grand implements WakeUpListener {
22 
23     public void performAction(WakeUpEvent wakeUpEvent) {
24         System.out.println("..hug..");
25     }
26 
27 }

第四步:定义事件类Event,Event事件类,将观察者状态的改变封装成Event类,不同的状态对应不同的事件。在我们的例子中,小孩的状态发生改变,他的观察者Dad、Dog、Grand会针对此事件做出反应;

1 class WakeUpEvent {
 2     //描述了事件的一些基本的信息:时间+地点+被观察对象
 3     private long time;
 4     private String location;
 5     private Child child;
 6 
 7     public WakeUpEvent(long time, String location, Child child) {
 8         super();
 9         this.time = time;
10         this.location = location;
11         this.child = child;
12     }
13 
14     public long getTime() {
15         return time;
16     }
17 
18     public void setTime(long time) {
19         this.time = time;
20     }
21 
22     public String getLocation() {
23         return location;
24     }
25 
26     public void setLocation(String location) {
27         this.location = location;
28     }
29 
30     public Child getChild() {
31         return child;
32     }
33 
34     public void setChild(Child child) {
35         this.child = child;
36     }
37 
38 }

第五步:下面的observers是我们的配置文件的文件名,尽量将这些动作的实现对客户端隐藏,用户不需要明白加载读取配合文件的操作,在做代码设计的时候要始终坚持这一原则。

1 try {
2             props.load(ObserveTest.class.getClassLoader().getResourceAsStream(
3                     "Observers.properties"));
4         } catch (IOException e) {
5             e.printStackTrace();
6         }

我们在这里将读取配置文件的动作封装在类中:

1 //这里将读取配置文件Observers.properties的操作封装在类中,使用静态代码块的形式,在类加载的时候将配置文件加载进内存
 2 //提高代码的灵活行,避免反复的执行加载配置文件的操作
 3 class PropertyMgr {
 4     // 重要的思想:缓存
 5     // 单例初步以及缓存:把硬盘上的内容缓存到内存上
 6     // 缓存的策略:访问最多的文件进行缓存
 7     private static Properties props = new Properties();
 8     // 这里使用了静态代码块,类加载的时候初始化一次
 9     static {
10         try {
11             props.load(ObserveTest.class.getClassLoader().getResourceAsStream(
12                     "Observers.properties"));
13         } catch (IOException e) {
14             e.printStackTrace();
15         }
16     }
17     //定义成静态static方法,方便在类外直接访问
18     public static String getProperty(String key) throws IOException {
19         return props.getProperty(key);
20 
21     }
22 }

最后一步:测试一下我们的程序,这里我给出完整的代码,方便读者的调试验证(这里附上我们的配置文件Observers.properties),只是一个简单的键值对应关系:observers=Grand,Dog,Dad

1 import java.io.IOException;
  2 import java.util.ArrayList;
  3 import java.util.List;
  4 import java.util.Properties;
  5 
  6 import org.omg.CORBA.PRIVATE_MEMBER;
  7 
  8 //Event事件类,将观察者状态的改变封装成Event类,不同的状态对应不同的事件
  9 //在我们的例子中,小孩的状态发生改变,他的观察者Dad、Dog、Grand会针对此事件做出反应
 10 class WakeUpEvent {
 11     //描述了事件的一些基本的信息:时间+地点+被观察对象
 12     private long time;
 13     private String location;
 14     private Child child;
 15 
 16     public WakeUpEvent(long time, String location, Child child) {
 17         super();
 18         this.time = time;
 19         this.location = location;
 20         this.child = child;
 21     }
 22 
 23     public long getTime() {
 24         return time;
 25     }
 26 
 27     public void setTime(long time) {
 28         this.time = time;
 29     }
 30 
 31     public String getLocation() {
 32         return location;
 33     }
 34 
 35     public void setLocation(String location) {
 36         this.location = location;
 37     }
 38 
 39     public Child getChild() {
 40         return child;
 41     }
 42 
 43     public void setChild(Child child) {
 44         this.child = child;
 45     }
 46 
 47 }
 48 
 49 //观察者模式中的Subject(目标),被观察对象
 50 
 51 class Child implements Runnable {
 52     //同List来存放不同的监听
 53     private List<WakeUpListener> wakeUpListeners = new ArrayList<WakeUpListener>();
 54 
 55     //List中添加监听的操作
 56     public void addWakenUpListener(WakeUpListener l) {
 57         wakeUpListeners.add(l);
 58     }
 59 
 60     //被观察者小孩的状态发生改变,则会通知观察者,使其执行各自的performAction方法
 61     public void wakeUp() {
 62         for (int i = 0; i < wakeUpListeners.size(); i++) {
 63             WakeUpListener l = wakeUpListeners.get(i);
 64             //
 65             l.performAction(new WakeUpEvent(System.currentTimeMillis(), "沙发上",
 66                     this));
 67         }
 68     }
 69 
 70     //监听线程的run()
 71     public void run() {
 72         try {
 73             Thread.sleep(1000);
 74         } catch (InterruptedException e) {
 75             e.printStackTrace();
 76         }
 77         this.wakeUp();
 78     }
 79 
 80 }
 81 /*
 82  * 具体观察者ConcreteObserver,被观察的对象child的状态发生变化这一事件会触发观察者的performAction方法,针对被观察者的变化做出反应
 83  */
 84 //具体观察者一
 85 class Dad implements WakeUpListener {
 86 
 87     public void performAction(WakeUpEvent wakeUpEvent) {
 88         System.out.println("..feed..");
 89     }
 90 
 91 }
 92 //具体观察者二
 93 class Dog implements WakeUpListener {
 94 
 95     public void performAction(WakeUpEvent wakeUpEvent) {
 96         System.out.println("..汪汪..");
 97     }
 98 
 99 }
100 //具体观察者三
101 class Grand implements WakeUpListener {
102 
103     public void performAction(WakeUpEvent wakeUpEvent) {
104         System.out.println("..hug..");
105     }
106 
107 }
108 //抽象的观察Observer
109 interface WakeUpListener {
110     public void performAction(WakeUpEvent wakeUpEvent);
111 }
112 
113 public class ObserveTest {
114 
115     /**
116      * @param args
117      * @throws IOException
118      * @throws ClassNotFoundException
119      * @throws IllegalAccessException
120      * @throws InstantiationException
121      */
122     public static void main(String[] args) throws Exception {
123         //读取配置文件的操作改成了静态方法,使用的时候直接调用,下面的observers是我们的配置文件的文件名
124         String observers[] = PropertyMgr.getProperty("observers").split(",");
125         Child child = new Child();
126         for (String s : observers) {
127             child.addWakenUpListener((WakeUpListener) Class.forName(s)
128                     .newInstance());
129         }
130         new Thread(child).start();
131     }
132 }
133 
134 //这里将读取配置文件Observers.properties的操作封装在类中,使用静态代码块的形式,在类加载的时候将配置文件加载进内存
135 //提高代码的灵活行,避免反复的执行加载配置文件的操作
136 class PropertyMgr {
137     // 重要的思想:缓存
138     // 单例初步以及缓存:把硬盘上的内容缓存到内存上
139     // 缓存的策略:访问最多的文件进行缓存
140     private static Properties props = new Properties();
141     // 这里使用了静态代码块,类加载的时候初始化一次
142     static {
143         try {
144             props.load(ObserveTest.class.getClassLoader().getResourceAsStream(
145                     "Observers.properties"));
146         } catch (IOException e) {
147             e.printStackTrace();
148         }
149     }
150     //定义成静态static方法,方便在类外直接访问
151     public static String getProperty(String key) throws IOException {
152         return props.getProperty(key);
153 
154     }
155 }

运行结果:

..hug..
..汪汪..
..feed..

第二部分

面试的过程可能会问到什么是观察者模式,其实这个时候不要给他们说什么太过于理论性的东西,举例子最方便不过了。观察者模式在java中的运用其实挺多的,比如说AWT和Swing中的监听机制用到的就是观察者模式,下面我们就来模拟一下看看监听机制是如何运作的。【注意】,代码中用到的类和方法都是我们自己定义的,不是调用API中的类和方法。

第一步:给出被监听对象:我们定义的一个按钮button

1 //首先定义一个按钮
 2 class Button {
 3     //创建一个具体的事件对象
 4     ActionEvent e = new ActionEvent(System.currentTimeMillis(), this);
 5     //List存储不同的监听者对象
 6     private List<ActionListener> actionListeners = new ArrayList<ActionListener>();
 7 
 8     //button按钮被按下时所触发的动作
 9     public void buttonPressed() {
10         for (int i = 0; i < actionListeners.size(); i++) {
11             ActionListener l = actionListeners.get(i);
12             //按下button,监听者会做出相应的动作
13             l.actionPerformed(e);
14         }
15     }
16 
17     //add添加监听者的动作
18     public void addActionListener(ActionListener l) {
19         actionListeners.add(l);
20     }
21 }

第二步:定义监听接口,具体的监听者去实现这个接口“

1 interface ActionListener {
2     public void actionPerformed(ActionEvent e);
3 }

第三步:具体的监听者

 在这里我们定义了两个监听者类

1 class MyActionListener implements ActionListener {
 2 
 3     public void actionPerformed(ActionEvent E) {
 4         System.out.println("button pressed");
 5     }
 6 }
 7 
 8 class MyActionListener2 implements ActionListener {
 9 
10     public void actionPerformed(ActionEvent E) {
11         System.out.println("button pressed2");
12     }
13 }

第四步:定义监听事件Event,时间对象包括:时间的发生时间when+事件源

1 class ActionEvent {
 2     long when;
 3     Object source;
 4 
 5     public ActionEvent(long when, Object source) {
 6         super();
 7         this.when = when;
 8     }
 9 
10     public long getWhen() {
11         return when;
12     }
13 
14     public Object getSource() {
15         return source;
16     }
17 }

第五步:给出测试代码

1 public class Test {
2     public static void main(String args[]) {
3 
4         Button b = new Button();
5         b.addActionListener(new MyActionListener());
6         b.addActionListener(new MyActionListener2());
7         b.buttonPressed();
8     }
9 }

运行结果:

button pressed
button pressed2

最后给出完整代码方便理解调试:

1 package com.observer.awt;
 2 
 3 import java.util.ArrayList;
 4 
 5 import java.util.List;
 6 
 7 public class Test {
 8     public static void main(String args[]) {
 9 
10         Button b = new Button();
11         b.addActionListener(new MyActionListener());
12         b.addActionListener(new MyActionListener2());
13         b.buttonPressed();
14     }
15 }
16 
17 //首先定义一个按钮
18 class Button {
19     //创建一个具体的事件对象
20     ActionEvent e = new ActionEvent(System.currentTimeMillis(), this);
21     //List存储不同的监听者对象
22     private List<ActionListener> actionListeners = new ArrayList<ActionListener>();
23 
24     //button按钮被按下时所触发的动作
25     public void buttonPressed() {
26         for (int i = 0; i < actionListeners.size(); i++) {
27             ActionListener l = actionListeners.get(i);
28             //按下button,监听者会做出相应的动作
29             l.actionPerformed(e);
30         }
31     }
32 
33     //add添加监听者的动作
34     public void addActionListener(ActionListener l) {
35         actionListeners.add(l);
36     }
37 }
38 
39 class MyActionListener implements ActionListener {
40 
41     public void actionPerformed(ActionEvent E) {
42         System.out.println("button pressed");
43     }
44 }
45 
46 class MyActionListener2 implements ActionListener {
47 
48     public void actionPerformed(ActionEvent E) {
49         System.out.println("button pressed2");
50     }
51 }
52 
53 interface ActionListener {
54     public void actionPerformed(ActionEvent e);
55 }
56 
57 class ActionEvent {
58     long when;
59     Object source;
60 
61     public ActionEvent(long when, Object source) {
62         super();
63         this.when = when;
64     }
65 
66     public long getWhen() {
67         return when;
68     }
69 
70     public Object getSource() {
71         return source;
72     }
73 }

第三部分:我们在第二步给出了我们自己模拟的按钮按下触发相应动作的过程,第三部分给出AWT中,调用API中封装的一些已经实现好的类和方法,和第二步完成的是相同的动作。

1 package com.observer.awt;
 2 
 3 import java.awt.Button;
 4 import java.awt.Frame;
 5 import java.awt.event.ActionEvent;
 6 import java.awt.event.ActionListener;
 7 import java.awt.event.WindowAdapter;
 8 
 9 public class TestFram extends Frame {
10     public void lanch() {
11         // 定义一个按钮,按钮显示的信息是"press me"
12         Button b = new Button("press me");
13         // 添加具体监听者
14         b.addActionListener(new MyActionListener());
15         b.addActionListener(new MyActionListener2());
16         // add方法将我们定义的button加入到Frame框架中
17         this.add(b);
18         // pack(),调整窗体的大小,里面可以添加参数
19         this.pack();
20         // 我们定义的TestFrame框架添加窗口监听
21         this.addWindowListener(new WindowAdapter() {
22         });
23         // 使窗体可见
24         this.setVisible(true);
25     }
26 
27     public static void main(String args[]) {
28         // 调用TestFram中的lanch方法,在Frame框架中定义一个按钮
29         new TestFram().lanch();
30     }
31 
32     // 具体的监听者
33     private class MyActionListener implements ActionListener {
34 
35         public void actionPerformed(ActionEvent e) {
36             // 监听者1观察到按钮被按下,做出反应
37             System.out.println("button pressed");
38         }
39     }
40 
41     private class MyActionListener2 implements ActionListener {
42 
43         public void actionPerformed(ActionEvent e) {
44             // 监听者2观察到按钮被按下,做出反应
45             System.out.println("button pressed 2!");
46         }
47     }
48 }