/ 设计模式 / 观察者模式 / PHP PHP 观察者模式讲解和代码示例 观察者是一种行为设计模式, 允许一个对象将其状态的改变通知其他对象 观察者模式提供了一种作用于任何实现了订阅者接口的对象的机制, 可对其事件进行订阅和取消订阅。 进一步了解观察者模式 导航 简介 概念示例 index Output 真实世界示例 index Output 复杂度: 流行度: 使用示例: PHP 中包含几个内置接口 (SplSubject、 SplObserver), 它们能让你的观察器模式实现与其他 PHP 代码兼容。 识别方法: 该模式可以通过将对象存储在列表中的订阅方法, 和对于面向该列表中对象的更新方法的调用来识别。 概念示例 真实世界示例 概念示例 本例说明了观察者设计模式的结构并重点回答了下面的问题: 它由哪些类组成? 这些类扮演了哪些角色? 模式中的各个元素会以何种方式相互关联? 了解该模式的结构后, 你可以更轻松地理解下面基于真实世界的 PHP 应用案例。 index.php: 概念示例 <?php namespace RefactoringGuru\Observer\Conceptual; /** * PHP has a couple of built-in interfaces related to the Observer pattern. * * Here's what the Subject interface looks like: * * @link http://php.net/manual/en/class.splsubject.php * * interface SplSubject * { * // Attach an observer to the subject. * public function attach(SplObserver $observer); * * // Detach an observer from the subject. * public function detach(SplObserver $observer); * * // Notify all observers about an event. * public function notify(); * } * * There's also a built-in interface for Observers: * * @link http://php.net/manual/en/class.splobserver.php * * interface SplObserver * { * public function update(SplSubject $subject); * } */ /** * The Subject owns some important state and notifies observers when the state * changes. */ class Subject implements \SplSubject { /** * @var int For the sake of simplicity, the Subject's state, essential to * all subscribers, is stored in this variable. */ public $state; /** * @var \SplObjectStorage List of subscribers. In real life, the list of * subscribers can be stored more comprehensively (categorized by event * type, etc.). */ private $observers; public function __construct() { $this->observers = new \SplObjectStorage(); } /** * The subscription management methods. */ public function attach(\SplObserver $observer): void { echo "Subject: Attached an observer.\n"; $this->observers->attach($observer); } public function detach(\SplObserver $observer): void { $this->observers->detach($observer); echo "Subject: Detached an observer.\n"; } /** * Trigger an update in each subscriber. */ public function notify(): void { echo "Subject: Notifying observers...\n"; foreach ($this->observers as $observer) { $observer->update($this); } } /** * Usually, the subscription logic is only a fraction of what a Subject can * really do. Subjects commonly hold some important business logic, that * triggers a notification method whenever something important is about to * happen (or after it). */ public function someBusinessLogic(): void { echo "\nSubject: I'm doing something important.\n"; $this->state = rand(0, 10); echo "Subject: My state has just changed to: {$this->state}\n"; $this->notify(); } } /** * Concrete Observers react to the updates issued by the Subject they had been * attached to. */ class ConcreteObserverA implements \SplObserver { public function update(\SplSubject $subject): void { if ($subject->state < 3) { echo "ConcreteObserverA: Reacted to the event.\n"; } } } class ConcreteObserverB implements \SplObserver { public function update(\SplSubject $subject): void { if ($subject->state == 0 || $subject->state >= 2) { echo "ConcreteObserverB: Reacted to the event.\n"; } } } /** * The client code. */ $subject = new Subject(); $o1 = new ConcreteObserverA(); $subject->attach($o1); $o2 = new ConcreteObserverB(); $subject->attach($o2); $subject->someBusinessLogic(); $subject->someBusinessLogic(); $subject->detach($o2); $subject->someBusinessLogic(); Output.txt: 执行结果 Subject: Attached an observer. Subject: Attached an observer. Subject: I'm doing something important. Subject: My state has just changed to: 2 Subject: Notifying observers... ConcreteObserverA: Reacted to the event. ConcreteObserverB: Reacted to the event. Subject: I'm doing something important. Subject: My state has just changed to: 4 Subject: Notifying observers... ConcreteObserverB: Reacted to the event. Subject: Detached an observer. Subject: I'm doing something important. Subject: My state has just changed to: 1 Subject: Notifying observers... ConcreteObserverA: Reacted to the event. 真实世界示例 在本例中, 观察者模式允许多种对象观察程序用户仓库中发生的事件。 仓库发出各种类型的事件并允许观察者监听所有或个别事件。 index.php: 真实世界示例 <?php namespace RefactoringGuru\Observer\RealWorld; /** * The UserRepository represents a Subject. Various objects are interested in * tracking its internal state, whether it's adding a new user or removing one. */ class UserRepository implements \SplSubject { /** * @var array The list of users. */ private $users = []; // Here goes the actual Observer management infrastructure. Note that it's // not everything that our class is responsible for. Its primary business // logic is listed below these methods. /** * @var array */ private $observers = []; public function __construct() { // A special event group for observers that want to listen to all // events. $this->observers["*"] = []; } private function initEventGroup(string $event = "*"): void { if (!isset($this->observers[$event])) { $this->observers[$event] = []; } } private function getEventObservers(string $event = "*"): array { $this->initEventGroup($event); $group = $this->observers[$event]; $all = $this->observers["*"]; return array_merge($group, $all); } public function attach(\SplObserver $observer, string $event = "*"): void { $this->initEventGroup($event); $this->observers[$event][] = $observer; } public function detach(\SplObserver $observer, string $event = "*"): void { foreach ($this->getEventObservers($event) as $key => $s) { if ($s === $observer) { unset($this->observers[$event][$key]); } } } public function notify(string $event = "*", $data = null): void { echo "UserRepository: Broadcasting the '$event' event.\n"; foreach ($this->getEventObservers($event) as $observer) { $observer->update($this, $event, $data); } } // Here are the methods representing the business logic of the class. public function initialize($filename): void { echo "UserRepository: Loading user records from a file.\n"; // ... $this->notify("users:init", $filename); } public function createUser(array $data): User { echo "UserRepository: Creating a user.\n"; $user = new User(); $user->update($data); $id = bin2hex(openssl_random_pseudo_bytes(16)); $user->update(["id" => $id]); $this->users[$id] = $user; $this->notify("users:created", $user); return $user; } public function updateUser(User $user, array $data): User { echo "UserRepository: Updating a user.\n"; $id = $user->attributes["id"]; if (!isset($this->users[$id])) { return null; } $user = $this->users[$id]; $user->update($data); $this->notify("users:updated", $user); return $user; } public function deleteUser(User $user): void { echo "UserRepository: Deleting a user.\n"; $id = $user->attributes["id"]; if (!isset($this->users[$id])) { return; } unset($this->users[$id]); $this->notify("users:deleted", $user); } } /** * Let's keep the User class trivial since it's not the focus of our example. */ class User { public $attributes = []; public function update($data): void { $this->attributes = array_merge($this->attributes, $data); } } /** * This Concrete Component logs any events it's subscribed to. */ class Logger implements \SplObserver { private $filename; public function __construct($filename) { $this->filename = $filename; if (file_exists($this->filename)) { unlink($this->filename); } } public function update(\SplSubject $repository, string $event = null, $data = null): void { $entry = date("Y-m-d H:i:s") . ": '$event' with data '" . json_encode($data) . "'\n"; file_put_contents($this->filename, $entry, FILE_APPEND); echo "Logger: I've written '$event' entry to the log.\n"; } } /** * This Concrete Component sends initial instructions to new users. The client * is responsible for attaching this component to a proper user creation event. */ class OnboardingNotification implements \SplObserver { private $adminEmail; public function __construct($adminEmail) { $this->adminEmail = $adminEmail; } public function update(\SplSubject $repository, string $event = null, $data = null): void { // mail($this->adminEmail, // "Onboarding required", // "We have a new user. Here's his info: " .json_encode($data)); echo "OnboardingNotification: The notification has been emailed!\n"; } } /** * The client code. */ $repository = new UserRepository(); $repository->attach(new Logger(__DIR__ . "/log.txt"), "*"); $repository->attach(new OnboardingNotification("1@example.com"), "users:created"); $repository->initialize(__DIR__ . "/users.csv"); // ... $user = $repository->createUser([ "name" => "John Smith", "email" => "john99@example.com", ]); // ... $repository->deleteUser($user); Output.txt: 执行结果 UserRepository: Loading user records from a file. UserRepository: Broadcasting the 'users:init' event. Logger: I've written 'users:init' entry to the log. UserRepository: Creating a user. UserRepository: Broadcasting the 'users:created' event. OnboardingNotification: The notification has been emailed! Logger: I've written 'users:created' entry to the log. UserRepository: Deleting a user. UserRepository: Broadcasting the 'users:deleted' event. Logger: I've written 'users:deleted' entry to the log. 概念示例 真实世界示例