概述
观察者模式(有时又被称为发布/订阅模式)是软体设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实作事件处理系统。
先看一个例子
有个小孩在睡觉,醒来之后要喂奶。
我们使用的是java,所以不要闹出下面的笑话(披着面向对象的面向过程):
1
2
3
4
5
6
7
|
public
class
Simulation {
public
static
void
main(String... args) {
//小孩睡觉
//起来之后爸爸喂奶
//...
}
}
|
我们根据面向对象思想,加上多线程模拟Child和Dad,小孩在睡觉,随时可以起来,Dad隔一段时间看下小孩是否醒来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
package
observer;
import
java.util.Random;
class
Child
implements
Runnable {
public
static
Random r =
new
Random();
private
boolean
wake =
false
;
public
Child() {
new
Thread(
this
).start();
}
@Override
public
void
run() {
while
(!wake) {
System.out.println(
"Child:I am sleeping..."
);
try
{
Thread.sleep(
1000
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
if
(r.nextInt(
10
) >
8
) {
wakeUp();
}
}
}
public
void
wakeUp() {
wake =
true
;
}
public
boolean
isWake() {
return
wake;
}
}
class
Dad
implements
Runnable {
private
Child c;
public
Dad(Child c) {
new
Thread(
this
).start();
this
.c = c;
}
@Override
public
void
run() {
while
(!c.isWake()) {
System.out.println(
"Dad:child is sleeping..."
);
try
{
Thread.sleep(
1000
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
}
feed(c);
}
private
void
feed(Child c2) {
System.out.println(
"feed child!"
);
}
}
public
class
FirstQuestion {
public
static
void
main(String[] args) {
new
Dad(
new
Child());
}
}
|
这样可以实现功能,但造成资源的浪费。开了这么多线程,Dad时间都用在看小孩身上了,Dad下午打牌的计划泡汤了。我们可以很容易的把Dad解放出来:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
class
Child
implements
Runnable {
public
static
Random r =
new
Random();
private
Dad d;
private
boolean
wake =
false
;
public
Child(Dad d) {
this
.d = d;
}
@Override
public
void
run() {
while
(!wake) {
System.out.println(
"Child:I am sleeping..."
);
try
{
Thread.sleep(
1000
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
if
(r.nextInt(
10
) >
8
) {
wakeUp();
}
}
}
public
void
wakeUp() {
wake =
true
;
d.feed(
this
);
}
public
boolean
isWake() {
return
wake;
}
}
class
Dad {
public
void
feed(Child c) {
System.out.println(
"feed child!"
);
}
}
public
class
FirstQuestion {
public
static
void
main(String[] args) {
new
Thread(
new
Child(
new
Dad())).start();
}
}
|
这样基本已经实现功能,稍微完善下,假如想知道小孩什么时候起来等一些信息,如果我们写在小孩类中就不太合适,所以我们抽象出类中WakenUpEvent:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
class
WakenUpEvent {
private
Date date;
private
String loc;
private
Dad dad;
public
WakenUpEvent(Date date, String loc, Dad dad) {
super
();
this
.date = date;
this
.loc = loc;
this
.dad = dad;
}
}
class
Child
implements
Runnable {
private
Dad d;
public
Child(Dad d) {
this
.d = d;
}
@Override
public
void
run() {
try
{
Thread.sleep(
5000
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
wakeUp();
}
public
void
wakeUp() {
d.actionToWakenUp(
new
WakenUpEvent(
new
Date(),
"child"
, d));
}
}
class
Dad {
public
void
actionToWakenUp(WakenUpEvent wakenUpEvent) {
System.out.println(
"child feed!"
);
}
}
public
class
FirstQuestion {
public
static
void
main(String[] args) {
new
Thread(
new
Child(
new
Dad())).start();
}
}
|
这样Dad可以做自己事,只要听到孩子声音,就过来喂奶。似乎问题已经解决,假如小孩醒后,小孩的爷爷想抱下,小孩家的小狗要叫下,等等,如果按照上面的设计,小孩需要持有爷爷GrandFather、狗Dog等的引用,再调用用响应的处理方法…需要修改较大的篇幅。实际上我们可以在小孩中使用一个集合存储所有监听小孩的对象,当小孩醒后,小孩依次调用监听者处理方法。要实现统一的接口,以可以被小孩监听器集合引用和调用相应方法,我们使用接口interface。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
class
WakenUpEvent {
private
Date date;
private
String eventType;
private
Object source;
public
WakenUpEvent(Date date, String eventType, Object source) {
this
.date = date;
this
.eventType = eventType;
this
.source = source;
}
}
class
Child
implements
Runnable {
private
List<WakenUpListener> list =
new
ArrayList<WakenUpListener>();
public
void
addWakenUpListener(WakenUpListener l) {
list.add(l);
}
@Override
public
void
run() {
System.out.println(
"child is sleeping..."
);
try
{
Thread.sleep(
2000
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
wakeUp();
}
public
void
wakeUp() {
for
(WakenUpListener l : list) {
l.actionWakenUp(
new
WakenUpEvent(
new
Date(),
""
,
this
));
}
}
}
interface
WakenUpListener {
void
actionWakenUp(WakenUpEvent e);
}
class
Dad
implements
WakenUpListener {
@Override
public
void
actionWakenUp(WakenUpEvent e) {
System.out.println(
"dad feed child!"
);
}
}
class
GrandFather
implements
WakenUpListener {
@Override
public
void
actionWakenUp(WakenUpEvent e) {
System.out.println(
"grandfather holl child!"
);
}
}
public
class
FirstQuestion {
public
static
void
main(String[] args) {
Child c =
new
Child();
c.addWakenUpListener(
new
Dad());
c.addWakenUpListener(
new
GrandFather());
new
Thread(c).start();
}
}
|
这个时候再看概述的例子,比较容易理解了吧!
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。[GOF 《设计模式》]
观察者模式中的推模式与拉模式[摘录]
在Observer模式中区分推模式和拉模式,先简单的解释一下两者的区别:推模式是当有消息时,把消息信息以参数的形式传递(推)给所有观察者,而拉模式是当有消息时,通知消息的方法本身并不带任何的参数,是由观察者自己到主体对象那儿取回(拉)消息。知道了这一点,大家可能很容易发现上面我所举的例子其实是一种推模式的Observer模式。我们先看看这种模式带来了什么好处:当有消息时,所有的观察者都会直接得到全部的消息,并进行相应的处理程序,与主体对象没什么关系,两者之间的关系是一种松散耦合。但是它也有缺陷,第一是所有的观察者得到的消息是一样的,也许有些信息对某个观察者来说根本就用不上,也就是观察者不能“按需所取”;第二,当通知消息的参数有变化时,所有的观察者对象都要变化。鉴于以上问题,拉模式就应运而生了,它是由观察者自己主动去取消息,需要什么信息,就可以取什么,不会像推模式那样得到所有的消息参数。OK,说到这儿,你是否对于推模式和拉模式有了一点了解呢?
实际上上面的代码中,因java中awt事件的影响,我在Event中加入了source字段,这算是拉模式的一种体现。我们可以得到公共的事件信息,也可以通过source得到发出事件对象的信息。
AWT事件模拟
说到AWT事件,我们根据上面的思路模拟下awt事件处理,观察者模式实现awt事件功能更加简单优雅,然而真正的awt也需要windows本身的事件驱动的支持,比如你按下某个button,首先windows捕获这个消息,把消息分发给java虚拟机,虚拟机在调用button相应的处理,button调用监听器处理(个人理解)。一般awt事件处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public
class
AwtButton
extends
Frame {
public
void
lanch() {
Button b =
new
Button(
"test"
);
b.addActionListener(
new
MyActionListener());
b.addActionListener(
new
MyActionListener1());
this
.add(b);
this
.addWindowListener(
new
WindowAdapter() {
@Override
public
void
windowClosing(WindowEvent e) {
System.exit(
0
);
}
});
setSize(
100
,
100
);
setVisible(
true
);
}
public
static
void
main(String[] args) {
new
AwtButton().lanch();
}
class
MyActionListener
implements
ActionListener {
@Override
public
void
actionPerformed(ActionEvent e) {
System.out.println(
"button pressed!"
);
}
}
class
MyActionListener1
implements
ActionListener {
@Override
public
void
actionPerformed(ActionEvent e) {
System.out.println(
"button pressed1!"
);
}
}
}
|
结合上面的孩子的例子,我们使用控制台模拟awt事件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
public
class
SimulationAwtButton {
public
static
void
main(String[] args) {
Button b =
new
Button();
b.addActionListener(
new
MyActionListener1());
b.addActionListener(
new
MyActionListener2());
b.pressed();
}
}
class
Button {
private
List<ActionListener> list =
new
ArrayList<ActionListener>();
public
void
addActionListener(ActionListener l) {
list.add(l);
}
public
void
pressed() {
ActionEvent e =
new
ActionEvent(
this
);
for
(ActionListener l : list) {
l.actionPerform(e);
}
}
}
interface
ActionListener {
void
actionPerform(ActionEvent e) ;
}
class
ActionEvent {
private
long
time;
private
Object source;
public
ActionEvent(Object source) {
this
.time = System.currentTimeMillis();
this
.source = source;
}
public
long
getTime() {
return
time;
}
public
Object getSource() {
return
source;
}
}
class
MyActionListener1
implements
ActionListener {
@Override
public
void
actionPerform(ActionEvent e) {
System.out.println(
"SimulationButton ActionPerformed:"
+e.getTime() + e.getSource());
}
}
class
MyActionListener2
implements
ActionListener {
@Override
public
void
actionPerform(ActionEvent e) {
System.out.println(
"SimulationButton ActionPerformed:"
+e.getTime() + e.getSource());
}
}
|
这样,对java送awt事件处理有了更深的认识。
适用性
1.当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
2.当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
3.当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
总结
通过Observer模式,把一对多对象之间的通知依赖关系的变得更为松散,大大地提高了程序的可维护性和可扩展性,也很好的符合了开放-封闭原则。