设计模式-观察者模式

在学习js事件的时候,说js事件其实就是观察者模式,所以打算先把观察者模式了解一下,这样就能更好的理解事件机制了。

由于观察者模式,其实比较好理解,这里就举wikipedia的例子
观察者模式的UML类图如下:

分析一下这个类图。主要分为两种类。一个是观察者类(Observer), 一个是被观察者类(Subject), 被观察者类有三种方法分别是:

  • addObserver(): 添加观察者到被观察者类中
  • deleteObserver(): 从被观察者类中删除观察者
  • notifyObserver(): 通知已经注册过的观察者

这里ConcreteSubjectAConcreteSubjectB是继承Subject的。当有多个被观察者类时可以用这种方式,当只有一个被观察者类时,可以直接用Subject进行实例话,不需要派生类。
Observer是观察者的抽象类。所有的观察者都继承自这个类。每个观察者类都要实现一个notify()函数。这个函数是当被观察者发生变化需要通知观察者的时候调用的,也就是notifyObserver()调用的。

代码事例:

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
class Student implements SplObserver{
protected $tipo = "Student";
private $nome;
private $endereco;
private $telefone;
private $email;
private $_classes = array();

public function GET_tipo() {
return $this->tipo;
}

public function GET_nome() {
return $this->nome;
}

public function GET_email() {
return $this->email;
}

public function GET_telefone() {
return $this->nome;
}

function __construct($nome) {
$this->nome = $nome;
}

public function update(SplSubject $object){
$object->SET_log("Comes from ".$this->nome.": I'm a student of ".$object->GET_materia());
}

}

class Teacher implements SplObserver{
protected $tipo = "Teacher";
private $nome;
private $endereco;
private $telefone;
private $email;
private $_classes = array();

public function GET_tipo() {
return $this->tipo;
}

public function GET_nome() {
return $this->nome;
}

public function GET_email() {
return $this->email;
}

public function GET_telefone() {
return $this->nome;
}

function __construct($nome) {
$this->nome = $nome;
}

public function update(SplSubject $object){
$object->SET_log("Comes from ".$this->nome.": I teach in ".$object->GET_materia());
}
}
class Subject implements SplSubject {
private $nome_materia;
private $_observadores = array();
private $_log = array();

public function GET_materia() {
return $this->nome_materia;
}

function SET_log($valor) {
$this->_log[] = $valor ;
}
function GET_log() {
return $this->_log;
}

function __construct($nome) {
$this->nome_materia = $nome;
$this->_log[] = " Subject $nome was included";
}
/* Adiciona um observador */
public function attach(SplObserver $classes) {
$this->_classes[] = $classes;
$this->_log[] = " The ".$classes->GET_tipo()." ".$classes->GET_nome()." was included";
}

/* Remove um observador */
public function detach(SplObserver $classes) {
foreach ($this->_classes as $key => $obj) {
if ($obj == $classes) {
unset($this->_classes[$key]);
$this->_log[] = " The ".$classes->GET_tipo()." ".$classes->GET_nome()." was removed";
}
}
}

/* Notifica os observadores */
public function notify(){
foreach ($this->_classes as $classes) {
$classes->update($this);
}
}
}

$subject = new Subject("Math");
$marcus = new Teacher("Marcus Brasizza");
$rafael = new Student("Rafael");
$vinicius = new Student("Vinicius");

// Include observers in the math Subject
$subject->attach($rafael);
$subject->attach($vinicius);
$subject->attach($marcus);

$subject2 = new Subject("English");
$renato = new Teacher("Renato");
$fabio = new Student("Fabio");
$tiago = new Student("tiago");

// Include observers in the english Subject
$subject2->attach($renato);
$subject2->attach($vinicius);
$subject2->attach($fabio);
$subject2->attach($tiago);

// Remove the instance "Rafael from subject"
$subject->detach($rafael);

// Notify both subjects
$subject->notify();
$subject2->notify();

echo "First Subject <br />";
echo "<pre>";
print_r($subject->GET_log());
echo "</pre>";
echo "<hr>";
echo "Second Subject <br />";
echo "<pre>";
print_r($subject2->GET_log());
echo "</pre>";

输出结果:

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

//First Subject

Array (

[0] => Subject Math was included
[1] => The Student Rafael was included
[2] => The Student Vinicius was included
[3] => The Teacher Marcus Brasizza was included
[4] => The Student Rafael was removed
[5] => Comes from Vinicius: I'm a student of Math
[6] => Comes from Marcus Brasizza: I teach in Math
)



//Second Subject

Array (

[0] => Subject English was included
[1] => The Teacher Renato was included
[2] => The Student Vinicius was included
[3] => The Student Fabio was included
[4] => The Student tiago was included
[5] => Comes from Renato: I teach in English
[6] => Comes from Vinicius: I'm a student of English

[7] => Comes from Fabio: I'm a student of English
[8] => Comes from tiago: I'm a student of English

)

结合代码我们再具体分析。上面的代码中有SplObserver是观察者基类,观察者类StudentTeacher都继承它。被观察者Subject继承自SplSubject基类,实现了三个函数:添加观察者的attach、删除观察者的detach及通知观察者的notify。我们还注意到每个观察者类都有一个update函数,这个函数是被观察者通知观察者时的接口,由notify函数调用并传递被观察者的信息到观察者。这就是一个完整的观察者模式的代码实例。

我们再看如何使用:
开始的时候初始化观察者和被观察者类:

1
2
3
4
$subject = new Subject("Math");
$marcus = new Teacher("Marcus Brasizza");
$rafael = new Student("Rafael");
$vinicius = new Student("Vinicius");

然后把观察者注册到被观察者中:

1
2
3
$subject->attach($rafael);
$subject->attach($vinicius);
$subject->attach($marcus);

接着又把$rafael从观察者类中删除了:

1
$subject->detach($rafael);

最后由被观察者通知注册的观察者做出改变:

1
$subject->notify();

所以最后的结果如下:

1
2
3
4
5
6
7
8
9
10
Array (

[0] => Subject Math was included
[1] => The Student Rafael was included
[2] => The Student Vinicius was included
[3] => The Teacher Marcus Brasizza was included
[4] => The Student Rafael was removed
[5] => Comes from Vinicius: I'm a student of Math
[6] => Comes from Marcus Brasizza: I teach in Math
)

第二个分析方法相同,不在赘述了。