观察者模式(Observer)

目的

当对象的状态发生变化时,所有依赖于它的对象都得到通知并被自动更新。它使用的是低耦合的方式。

例子

  • 使用观察者模式观察消息队列在 GUI 中的运行情况。

注意

PHP 已经定义了2个接口用于快速实现观察者模式:SplObserver 和 SplSubject。

UML 图

sWK78CV3NN.png

代码

你可以在 GitHub 查看这段代码。

User.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Observer;
  3. /**
  4. * User 实现观察者模式 (称为主体),它维护一个观察者列表,
  5. * 当对象发生变化时通知 User。
  6. */
  7. class User implements \SplSubject
  8. {
  9. /**
  10. * @var string
  11. */
  12. private $email;
  13. /**
  14. * @var \SplObjectStorage
  15. */
  16. private $observers;
  17. public function __construct()
  18. {
  19. $this->observers = new \SplObjectStorage();
  20. }
  21. public function attach(\SplObserver $observer)
  22. {
  23. $this->observers->attach($observer);
  24. }
  25. public function detach(\SplObserver $observer)
  26. {
  27. $this->observers->detach($observer);
  28. }
  29. public function changeEmail(string $email)
  30. {
  31. $this->email = $email;
  32. $this->notify();
  33. }
  34. public function notify()
  35. {
  36. /** @var \SplObserver $observer */
  37. foreach ($this->observers as $observer) {
  38. $observer->update($this);
  39. }
  40. }
  41. }

UserObserver.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Observer;
  3. class UserObserver implements \SplObserver
  4. {
  5. /**
  6. * @var User[]
  7. */
  8. private $changedUsers = [];
  9. /**
  10. * 它通常使用 SplSubject::notify() 通知主体
  11. *
  12. * @param \SplSubject $subject
  13. */
  14. public function update(\SplSubject $subject)
  15. {
  16. $this->changedUsers[] = clone $subject;
  17. }
  18. /**
  19. * @return User[]
  20. */
  21. public function getChangedUsers(): array
  22. {
  23. return $this->changedUsers;
  24. }
  25. }

测试

Tests/ObserverTest.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Observer\Tests;
  3. use DesignPatterns\Behavioral\Observer\User;
  4. use DesignPatterns\Behavioral\Observer\UserObserver;
  5. use PHPUnit\Framework\TestCase;
  6. class ObserverTest extends TestCase
  7. {
  8. public function testChangeInUserLeadsToUserObserverBeingNotified()
  9. {
  10. $observer = new UserObserver();
  11. $user = new User();
  12. $user->attach($observer);
  13. $user->changeEmail('foo@bar.com');
  14. $this->assertCount(1, $observer->getChangedUsers());
  15. }
  16. }