观察者模式

目的

观察者模式为对象实现“发布/订阅”功能,每当“主题”状态改变的时候,将通知所有被绑定的“观察者”。观察者模式用于减少耦合的对象数量,使用松散耦合来代替。

使用场景

  • 在 GUI 程序中,通过观察消息队列来显式作业执行进度

说明

PHP 中提供了两个可以帮助我们实现观察者模式的接口:SplObserverSplSubject

UML 类图

观察者模式类图

代码

User.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
<?php declare(strict_types = 1);

namespace DesignPatterns\Behavioral\Observer;

use SplSubject;
use SplObjectStorage;
use SplObserver;

/**
* User implements the observed object (called Subject), it maintains a list of observers and sends notifications to
* them in case changes are made on the User object
*/
class User implements SplSubject
{
private string $email;
private SplObjectStorage $observers;

public function __construct()
{
$this->observers = new SplObjectStorage();
}

public function attach(SplObserver $observer)
{
$this->observers->attach($observer);
}

public function detach(SplObserver $observer)
{
$this->observers->detach($observer);
}

public function changeEmail(string $email)
{
$this->email = $email;
$this->notify();
}

public function notify()
{
/** @var SplObserver $observer */
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
}

UserObserver.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
<?php declare(strict_types = 1);

namespace DesignPatterns\Behavioral\Observer;

use SplObserver;
use SplSubject;

class UserObserver implements SplObserver
{
/**
* @var SplSubject[]
*/
private array $changedUsers = [];

/**
* It is called by the Subject, usually by SplSubject::notify()
*/
public function update(SplSubject $subject)
{
$this->changedUsers[] = clone $subject;
}

/**
* @return SplSubject[]
*/
public function getChangedUsers(): array
{
return $this->changedUsers;
}
}

测试

Tests/ObserverTest.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php declare(strict_types = 1);

namespace DesignPatterns\Behavioral\Observer\Tests;

use DesignPatterns\Behavioral\Observer\User;
use DesignPatterns\Behavioral\Observer\UserObserver;
use PHPUnit\Framework\TestCase;

class ObserverTest extends TestCase
{
public function testChangeInUserLeadsToUserObserverBeingNotified()
{
$observer = new UserObserver();

$user = new User();
$user->attach($observer);

$user->changeEmail('foo@bar.com');
$this->assertCount(1, $observer->getChangedUsers());
}
}