观察者模式 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 }