观察者模式

问题:
观察者模式的核心是把客户元素(观察者)从一个中心类(主体)中分离开来。当主体知道事件发生时,观察者需要被通知到。同时,我们并不希望将主体与观察者之间的关系进行硬编码。这样,观察者的代码可被重复使用,不同主体可以随意组合使用多个观察者。

概念:
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有观察者对象,使它们能够自动更新自己。

实现一:

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
//主体类接口
interface Observable
{
    public function getStatus();
    public function attach(Observer $observer);
    public function detach(Observer $observer);
    public function notify();
}
//主体类实现
class Login implements Observable
{
    const LOGIN_USER_UNKNOWN = 1;
    const LOGIN_WRONG_PASS = 2;
    const LOGIN_ACCESS = 3;
    private $status;
    private $observers;
 
    public function __construct()
    {
        $this->status = [];
        $this->observers = [];
    }
 
    private function setStatus($status, $user, $ip)
    {
        $this->status = [$status, $user, $ip];
    }
 
    public function getStatus()
    {
        return $this->status;
    }
 
    public function handleLogin($user, $pass, $ip)
    {
        switch(rand(1, 3)) {
            case 1:
                $this->setStatus(self::LOGIN_USER_UNKNOWN, $user, $ip);
                $ret = false; break;
            case 2:
                $this->setStatus(self::LOGIN_WRONG_PASS, $user, $ip);
                $ret = false; break;
            case 3:
                $this->setStatus(self::LOGIN_ACCESS, $user, $ip);
                $ret = true; break;
        }
        $this->notify();
        return $ret;
    }
 
    //绑定观察者
    public function attach(Observer $observer)
    {
        $this->observers[] = $observer;
    }
 
    //解绑观察者
    public function detach(Observer $observer)
    {
        $newObservers = [];
        foreach($this->observers as $obs) {
            if($obs !== $observer) {
                $newObservers[] = $obs;
            }
        }
        $this->observers = $newObservers;
    }
 
    //通知观察者
    public function notify()
    {
        foreach($this->observers as $obs) {
            $obs->update($this);
        }
    }
}
 
//观察者接口
interface Observer
{
    public function update(Observable $observable);
}
//观察者实现
class SecurityMonitor implements Observer
{
    public function update(Observable $observable)
    {
        $status = $observable->getStatus();
        if($status[0] == Login::LOGIN_WRONG_PASS) {
            echo 'Sending mail to sysadmin<br>';
        }
    }
}
class GeneralLogger implements Observer
{
    public function update(Observable $observable)
    {
        $status = $observable->getStatus();
        if($status[0] == Login::LOGIN_WRONG_PASS) {
            echo 'Adding login data to log<br>';
        }
    }
}
class PartnershipTool implements Observer
{
    public function update(Observable $observable)
    {
        $status = $observable->getStatus();
        if($status[0] == Login::LOGIN_WRONG_PASS) {
            echo 'Setting cookie if IP matches a list<br>';
        }
    }
}
 
//应用
$login = new Login();
$login->attach(new SecurityMonitor());
$login->attach(new GeneralLogger());
$login->attach(new PartnershipTool());
$login->handleLogin('ajun', 'shiajunhaha', '127.0.0.1');

注:这里需要在SecurityMonitor、GeneralLogger和PartnershipTool类中调用Login::getStatus(),但是无法保证update()接收到的Observable对象一定是一个Login对象,所以为了保证所有的Observable对象都有getStatus()方法,这里采用扩展Observable接口,并在其中添加了getStatus()方法,这样是可以解决问题,但是降低了Observable接口的通用性。

实现二:
保持Observable接口的通用性,由Observer类负责保证它们的主体是正确的类型。
1. 代码实现:

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
//主体类接口
interface Observable
{
    public function attach(Observer $observer);
    public function detach(Observer $observer);
    public function notify();
}
//主体类实现
class Login implements Observable
{
    const LOGIN_USER_UNKNOWN = 1;
    const LOGIN_WRONG_PASS = 2;
    const LOGIN_ACCESS = 3;
    private $status;
    private $observers;
 
    public function __construct()
    {
        $this->status = [];
        $this->observers = [];
    }
 
    private function setStatus($status, $user, $ip)
    {
        $this->status = [$status, $user, $ip];
    }
 
    public function getStatus()
    {
        return $this->status;
    }
 
    public function handleLogin($user, $pass, $ip)
    {
        switch(rand(1, 3)) {
            case 1:
                $this->setStatus(self::LOGIN_USER_UNKNOWN, $user, $ip);
                $ret = false; break;
            case 2:
                $this->setStatus(self::LOGIN_WRONG_PASS, $user, $ip);
                $ret = false; break;
            case 3:
                $this->setStatus(self::LOGIN_ACCESS, $user, $ip);
                $ret = true; break;
        }
        $this->notify();
        return $ret;
    }
 
    //绑定观察者
    public function attach(Observer $observer)
    {
        $this->observers[] = $observer;
    }
 
    //解绑观察者
    public function detach(Observer $observer)
    {
        $newObservers = [];
        foreach($this->observers as $obs) {
            if($obs !== $observer) {
                $newObservers[] = $obs;
            }
        }
        $this->observers = $newObservers;
    }
 
    //通知观察者
    public function notify()
    {
        foreach($this->observers as $obs) {
            $obs->update($this);
        }
    }
}
 
//观察者接口
interface Observer
{
    public function update(Observable $observable);
}
//观察者抽象超类
abstract class LoginObserver implements Observer
{
    private $login;
 
    public function __construct(Login $login)
    {
        $this->login = $login;
        $login->attach($this);
    }
 
    public function update(Observable $observable)
    {
        if($observable === $this->login) {
            $this->doUpdate($observable);
        }
    }
 
    abstract public function doUpdate(Login $login);
}
//观察者实现
class SecurityMonitor extends LoginObserver
{
    public function doUpdate(Login $login)
    {
        $status = $login->getStatus();
        if($status[0] == Login::LOGIN_WRONG_PASS) {
            echo 'Sending mail to sysadmin<br>';
        }
    }
}
class GeneralLogger extends LoginObserver
{
    public function doUpdate(Login $login)
    {
        $status = $login->getStatus();
        if($status[0] == Login::LOGIN_WRONG_PASS) {
            echo 'Adding login data to log<br>';
        }
    }
}
class PartnershipTool extends LoginObserver
{
    public function doUpdate(Login $login)
    {
        $status = $login->getStatus();
        if($status[0] == Login::LOGIN_WRONG_PASS) {
            echo 'Setting cookie if IP matches a list<br>';
        }
    }
}
 
//应用
$login = new Login();
new SecurityMonitor($login);
new GeneralLogger($login);
new PartnershipTool($login);
$login->handleLogin('ajun', 'shiajunhaha', '127.0.0.1');

2. 类图:

实现三:
通过PHP内置的SPL扩展来实现观察者模式。主要涉及3个元素:SplObserver、SplSubject和SplObjectStorage,其中SplObserver和SplSubject是分别与“实现一”、“实现二”中的Observer和Observable完全相同的接口,而SplObjectStorage则是一个工具类,用于更好地存储、删除对象。
代码示例:

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
//主体类实现
class Login implements SplSubject
{
    const LOGIN_USER_UNKNOWN = 1;
    const LOGIN_WRONG_PASS = 2;
    const LOGIN_ACCESS = 3;
    private $status;
    private $storage;
 
    public function __construct()
    {
        $this->status = [];
        $this->storage = new SplObjectStorage();
    }
 
    private function setStatus($status, $user, $ip)
    {
        $this->status = [$status, $user, $ip];
    }
 
    public function getStatus()
    {
        return $this->status;
    }
 
    public function handleLogin($user, $pass, $ip)
    {
        switch(rand(1, 3)) {
            case 1:
                $this->setStatus(self::LOGIN_USER_UNKNOWN, $user, $ip);
                $ret = false; break;
            case 2:
                $this->setStatus(self::LOGIN_WRONG_PASS, $user, $ip);
                $ret = false; break;
            case 3:
                $this->setStatus(self::LOGIN_ACCESS, $user, $ip);
                $ret = true; break;
        }
        $this->notify();
        return $ret;
    }
 
    //绑定观察者
    public function attach(SplObserver $observer)
    {
        $this->storage->attach($observer);
    }
 
    //解绑观察者
    public function detach(SplObserver $observer)
    {
        $this->storage->attach($observer);
    }
 
    //通知观察者
    public function notify()
    {
        foreach($this->storage as $obs) {
            $obs->update($this);
        }
    }
}
 
//观察者抽象超类
abstract class LoginObserver implements SplObserver
{
    private $login;
 
    public function __construct(Login $login)
    {
        $this->login = $login;
        $login->attach($this);
    }
 
    public function update(SplSubject $subject)
    {
        if($subject === $this->login) {
            $this->doUpdate($subject);
        }
    }
 
    abstract public function doUpdate(Login $login);
}
//观察者实现
class SecurityMonitor extends LoginObserver
{
    public function doUpdate(Login $login)
    {
        $status = $login->getStatus();
        if($status[0] == Login::LOGIN_WRONG_PASS) {
            echo 'Sending mail to sysadmin<br>';
        }
    }
}
class GeneralLogger extends LoginObserver
{
    public function doUpdate(Login $login)
    {
        $status = $login->getStatus();
        if($status[0] == Login::LOGIN_WRONG_PASS) {
            echo 'Adding login data to log<br>';
        }
    }
}
class PartnershipTool extends LoginObserver
{
    public function doUpdate(Login $login)
    {
        $status = $login->getStatus();
        if($status[0] == Login::LOGIN_WRONG_PASS) {
            echo 'Setting cookie if IP matches a list<br>';
        }
    }
}
 
//应用
$login = new Login();
new SecurityMonitor($login);
new GeneralLogger($login);
new PartnershipTool($login);
$login->handleLogin('ajun', 'shiajunhaha', '127.0.0.1');

效果:
1. 当一个对象的改变需要同时改变其他对象,而且不知道具体有多少对象需要被改变的时候,就应该考虑使用观察者模式。
2. 观察者模式所做的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响另一方。

posted @   疯一样的狼人  阅读(134)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示