
PHP 中介者模式讲解和代码示例
中介者是一种行为设计模式, 让程序组件通过特殊的中介者对象进行间接沟通, 达到减少组件之间依赖关系的目的。
中介者能使得程序更易于修改和扩展, 而且能更方便地对独立的组件进行复用, 因为它们不再依赖于很多其他的类。
复杂度:
流行度:
使用示例: 纯粹的中介者模式实现在 PHP 中并不常见, 在其他语言 (例如 Java 或 C#) 中则专门在 GUI 上使用。 PHP 应用可能会包含数十个组件, 但是它们极少会在单次对话中直接通信。
但是, 许多 PHP 框架中的事件分发系统或 MVC 控制器的一些实现仍会使用中介者模式。
概念示例
本例说明了中介者设计模式的结构并重点回答了下面的问题:
- 它由哪些类组成?
- 这些类扮演了哪些角色?
- 模式中的各个元素会以何种方式相互关联?
了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 PHP 应用案例。
index.php: 概念示例
<?php namespace RefactoringGuru\Mediator\Conceptual; /** * The Mediator interface declares a method used by components to notify the * mediator about various events. The Mediator may react to these events and * pass the execution to other components. */ interface Mediator { public function notify(object $sender, string $event): void; } /** * Concrete Mediators implement cooperative behavior by coordinating several * components. */ class ConcreteMediator implements Mediator { private $component1; private $component2; public function __construct(Component1 $c1, Component2 $c2) { $this->component1 = $c1; $this->component1->setMediator($this); $this->component2 = $c2; $this->component2->setMediator($this); } public function notify(object $sender, string $event): void { if ($event == "A") { echo "Mediator reacts on A and triggers following operations:\n"; $this->component2->doC(); } if ($event == "D") { echo "Mediator reacts on D and triggers following operations:\n"; $this->component1->doB(); $this->component2->doC(); } } } /** * The Base Component provides the basic functionality of storing a mediator's * instance inside component objects. */ class BaseComponent { protected $mediator; public function __construct(Mediator $mediator = null) { $this->mediator = $mediator; } public function setMediator(Mediator $mediator): void { $this->mediator = $mediator; } } /** * Concrete Components implement various functionality. They don't depend on * other components. They also don't depend on any concrete mediator classes. */ class Component1 extends BaseComponent { public function doA(): void { echo "Component 1 does A.\n"; $this->mediator->notify($this, "A"); } public function doB(): void { echo "Component 1 does B.\n"; $this->mediator->notify($this, "B"); } } class Component2 extends BaseComponent { public function doC(): void { echo "Component 2 does C.\n"; $this->mediator->notify($this, "C"); } public function doD(): void { echo "Component 2 does D.\n"; $this->mediator->notify($this, "D"); } } /** * The client code. */ $c1 = new Component1(); $c2 = new Component2(); $mediator = new ConcreteMediator($c1, $c2); echo "Client triggers operation A.\n"; $c1->doA(); echo "\n"; echo "Client triggers operation D.\n"; $c2->doD();
Output.txt: 执行结果
Client triggers operation A. Component 1 does A. Mediator reacts on A and triggers following operations: Component 2 does C. Client triggers operation D. Component 2 does D. Mediator reacts on D and triggers following operations: Component 1 does B. Component 2 does C.
真实世界示例
在本例中, 中介者模式通过提供中心化的事件分发器来对观察者模式进行拓展。 它允许任何对象无需依赖另一个对象所属的类就能跟踪和触发其事件。
index.php: 真实世界示例
<?php namespace RefactoringGuru\Mediator\RealWorld; /** * The Event Dispatcher class acts as a Mediator and contains the subscription * and notification logic. While a classic Mediator often depends on concrete * component classes, this one is only tied to their abstract interfaces. * * We are able to achieve this level of indirection thanks to the way the * connections between components are established. The components themselves may * subscribe to specific events that they are interested in via the Mediator's * subscription interface. * * Note, we can't use the PHP's built-in Subject/Observer interfaces here * because we'll be stretching them too far from what they were designed for. */ class EventDispatcher { /** * @var array */ private $observers = []; public function __construct() { // The 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(Observer $observer, string $event = "*"): void { $this->initEventGroup($event); $this->observers[$event][] = $observer; } public function detach(Observer $observer, string $event = "*"): void { foreach ($this->getEventObservers($event) as $key => $s) { if ($s === $observer) { unset($this->observers[$event][$key]); } } } public function trigger(string $event, object $emitter, $data = null): void { echo "EventDispatcher: Broadcasting the '$event' event.\n"; foreach ($this->getEventObservers($event) as $observer) { $observer->update($event, $emitter, $data); } } } /** * A simple helper function to provide global access to the event dispatcher. */ function events(): EventDispatcher { static $eventDispatcher; if (!$eventDispatcher) { $eventDispatcher = new EventDispatcher(); } return $eventDispatcher; } /** * The Observer interface defines how components receive the event * notifications. */ interface Observer { public function update(string $event, object $emitter, $data = null); } /** * Unlike our Observer pattern example, this example makes the UserRepository * act as a regular component that doesn't have any special event-related * methods. Like any other component, this class relies on the EventDispatcher * to broadcast its events and listen for the other ones. * * @see \RefactoringGuru\Observer\RealWorld\UserRepository */ class UserRepository implements Observer { /** * @var array List of application's users. */ private $users = []; /** * Components can subscribe to events by themselves or by client code. */ public function __construct() { events()->attach($this, "users:deleted"); } /** * Components can decide whether they'd like to process an event using its * name, emitter or any contextual data passed along with the event. */ public function update(string $event, object $emitter, $data = null): void { switch ($event) { case "users:deleted": if ($emitter === $this) { return; } $this->deleteUser($data, true); break; } } // These methods represent the business logic of the class. public function initialize(string $filename): void { echo "UserRepository: Loading user records from a file.\n"; // ... events()->trigger("users:init", $this, $filename); } public function createUser(array $data, bool $silent = false): 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; if (!$silent) { events()->trigger("users:created", $this, $user); } return $user; } public function updateUser(User $user, array $data, bool $silent = false): ?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); if (!$silent) { events()->trigger("users:updated", $this, $user); } return $user; } public function deleteUser(User $user, bool $silent = false): void { echo "UserRepository: Deleting a user.\n"; $id = $user->attributes["id"]; if (!isset($this->users[$id])) { return; } unset($this->users[$id]); if (!$silent) { events()->trigger("users:deleted", $this, $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); } /** * All objects can trigger events. */ public function delete(): void { echo "User: I can now delete myself without worrying about the repository.\n"; events()->trigger("users:deleted", $this, $this); } } /** * This Concrete Component logs any events it's subscribed to. */ class Logger implements Observer { private $filename; public function __construct($filename) { $this->filename = $filename; if (file_exists($this->filename)) { unlink($this->filename); } } public function update(string $event, object $emitter, $data = null) { $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 Observer { private $adminEmail; public function __construct(string $adminEmail) { $this->adminEmail = $adminEmail; } public function update(string $event, object $emitter, $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(); events()->attach($repository, "facebook:update"); $logger = new Logger(__DIR__ . "/log.txt"); events()->attach($logger, "*"); $onboarding = new OnboardingNotification("1@example.com"); events()->attach($onboarding, "users:created"); // ... $repository->initialize(__DIR__ . "users.csv"); // ... $user = $repository->createUser([ "name" => "John Smith", "email" => "john99@example.com", ]); // ... $user->delete();
Output.txt: 执行结果
UserRepository: Loading user records from a file. EventDispatcher: Broadcasting the 'users:init' event. Logger: I've written 'users:init' entry to the log. UserRepository: Creating a user. EventDispatcher: Broadcasting the 'users:created' event. OnboardingNotification: The notification has been emailed! Logger: I've written 'users:created' entry to the log. User: I can now delete myself without worrying about the repository. EventDispatcher: Broadcasting the 'users:deleted' event. UserRepository: Deleting a user. Logger: I've written 'users:deleted' entry to the log.