PHP设计模式之观察者模式

观察者模式完美的将观察者和被观察的对象分离开。举个例子,用户界面可以作为一个观察者,业务数据是被观察者,用户界面观察业务数据的变化,发现数据变化后,就显示在界面上。面向对象设计的一个原则是:系统中的每个类将重点放在某一个功能上,而不是其他方面。一个对象只做一件事情,并且将他做好。观察者模式在模块之间划定了清晰的界限 ,提高了应用程序的可维护性和重用性。
实现观察者模式有很多形式,比较直观的一种是使用一种“注册——通知——撤销注册”的形式。

实例一:用户登录

管理员登录通知第一版:无需任何通知的登录

此版本就一个 Login 类包办了验证登录和获取登录信息的功能。

demo1.php

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
<?php
header('Content-Type:text/html; charset=utf-8');

class Login{
private $user = 'admin';
private $pass = '123456';
private $info;

public function handleLogin($user, $pass){
$this->info = [$user, $pass];
switch($this->check($this->info)){
case 1:
$ret = true;
break;
case 2:
$ret = false;
break;
}
return $ret;
}

public function check($info){
if($info[0] == $this->user && $info[1] == $this->pass){
return 1;
}
return 2;
}

public function getInfo(){
return $this->info;
}
}

$login = new Login();
echo $login->handleLogin('admin', '123456');
echo '<br />';
print_r($login->getInfo());

管理员登录通知第二版:增加了验证错误发送短信和记录到数据里的功能

增加发送邮件和记录数据库也可以包办到 Login 类里,但设计模式的原则是,一个类只处理一个功能,所以,可以关联另外两个类:MailWaring 类发送邮件类,Logger记录登录日志到数据库的类。

demo2.php

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
<?php
header('Content-Type:text/html; charset=utf-8');

class MailWarning{
static public function update($info){
echo '发送一封邮件给总管理员:用户名:'.$info[0].',密码:'.$info[1].'登录错误!';
}
}

class Logger{
static public function update($info){
echo '写入数据库:用户名:'.$info[0].',密码:'.$info[1].'!';
}
}

class Login{
private $user = 'admin';
private $pass = '123456';
private $info;

public function handleLogin($user, $pass){
$this->info = [$user, $pass];
switch($this->check($this->info)){
case 1:
$ret = true;
break;
case 2:
$ret = false;
break;
}
Logger::update($this->info);
if(!$ret) MailWarning::update($this->info);
return $ret;
}

public function check($info){
if($info[0] == $this->user && $info[1] == $this->pass){
return 1;
}
return 2;
}

public function getInfo(){
return $this->info;
}
}

$login = new Login();
echo $login->handleLogin('admin', '123456');
echo '<br />';
print_r($login->getInfo());

这个版本分成了三个类,有一个问题是,当扩展的时候,再增加一个叫做登录错误自动清理 cookies 的类,那么这个时候,你就必须在 Login 类里再写一个调用,这是非常危险的行为,也违背了面向对象类独立的原则。
还有一个问题,就是登录响应了发送邮件功能,写入日志功能,但如果我对与某个管理员不去做这些响应,而需要撤销,那这个第二版本就无能为力了。

管理员登录通知第三版:使用观察者模式注册和撤销响应

此版本设定了 Login.class.php 是被观察者,MailWaring.class.php 和 Logger 是观察者。
我们可以通过 attach()方法添加各种响应的观察者,也可以通过 detach()来撤销已有的观察
者。notify()用于响应每个添加进数组的观察者。

demo3.php

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
<?php
header('Content-Type:text/html; charset=utf-8');

// 观察者类
abstract class Observer{
abstract public function update(Obserable $obserable);
}

class MailWarning extends Observer{
public function update(Obserable $obserable){
$info = $obserable->getInfo();
$ret = $obserable->getRet();
if(!$ret){
echo '发送一封邮件给总管理员:用户名:'.$info[0].',密码:'.$info[1].'登录错误!';
}
}
}

class Logger extends Observer{
public function update(Obserable $obserable){
$info = $obserable->getInfo();
echo '写入数据库:用户名:'.$info[0].',密码:'.$info[1].'!';
}
}

// 被观察者
abstract class Obserable{
abstract public function attach(Observer $observer); // 注册观察者
abstract public function detach(Observer $observer); // 撤销观察者
abstract public function notify(); // 响应观察者
}

class Login extends Obserable{
private $user = 'admin';
private $pass = '123456';
private $ret;
private $info;
private $observers = [];

public function attach(Observer $observer){
$this->observers[] = $observer;
}

public function detach(Observer $observer){
if (in_array($observer, $this->_observers)) {
$this->observers = array_udiff($this->observers, array($observer),
create_function('$a,$b', 'return ($a === $b) ? 0:1;'));
}
}

public function notify(){
foreach($this->observers as $value){
$value->update($this);
}
}

public function handleLogin($user, $pass){
$this->info = [$user, $pass];
switch($this->check($this->info)){
case 1:
$this->ret = true;
break;
case 2:
$this->ret = false;
break;
}
$this->notify();
return $this->ret;
}

public function check($info){
if($info[0] == $this->user && $info[1] == $this->pass){
return 1;
}
return 2;
}

public function getInfo(){
return $this->info;
}

public function getRet(){
return $this->ret;
}
}

$login = new Login();
$mail_waring = new MailWarning();
$login->attach($mail_waring);
$login->attach(new Logger());
$login->detach($mail_waring);
echo $login->handleLogin('admin', '123456');

实例二:添加事件

demo4.php

1
2
3
4
5
6
7
8
9
10
11
<?php
header('Content-Type:text/html; charset=utf-8');

class Event{
public function trigger(){
echo 'Event<br />';
}
}

$event = new Event();
$event->trigger();

增加事件逻辑

demo5.php

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
header('Content-Type:text/html; charset=utf-8');

class Event{
public function trigger(){
echo 'Event<br />';
echo '添加逻辑1<br />';
echo '添加逻辑2<br />';
}
}

$event = new Event();
$event->trigger();

使用观察者模式增加事件逻辑

demo6.php

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
<?php
header('Content-Type:text/html; charset=utf-8');

interface Observer{
public function update($event_info = null);
}

abstract class EventGenerator{
private $observers = [];

public function addObserver(Observer $observer){
$this->observers[] = $observer;
}

public function notify(){
foreach($this->observers as $observer){
$observer->update();
}
}

}

class Observer1 implements Observer{
public function update($event_info = null){
echo '添加逻辑1<br />';
}
}

class Observer2 implements Observer{
public function update($event_info = null){
echo '添加逻辑2<br />';
}
}

class Event extends EventGenerator{
public function trigger(){
echo 'Event<br />';
$this->notify();
}
}

$event = new Event();
$event->addObserver(new Observer1());
$event->addObserver(new Observer2());
$event->trigger();

  1. 观察者模式(Observer),当一个对象状态发生改变时,依赖它的对象全部收到通知,并自动更新。
  2. 场景:一个事件发生后,要执行一连串更新操作。传统的编程方式,就是在事件的代码之后直接加入处理逻辑。当更新的逻辑增多之后,代码会变得难以维护。这种方式是耦合的,侵入式的,增加新的逻辑需要修改事件主体的代码。
  3. 观察者模式实现了低耦合,非侵入式的通知与更新机制。