Laravel 13 中介者模式深度解析

中介者模式是一种行为型设计模式,它通过将对象间的交互集中到一个中介者对象中,从而降低对象间的耦合度。本文将深入探讨 Laravel 13 中中介者模式的高级用法。

中介者模式基础

什么是中介者模式

中介者模式定义了一个对象,它封装了一组对象之间的交互方式,使对象之间不需要显式相互引用。

1
2
3
4
5
6
7
8
<?php

namespace App\Contracts;

interface MediatorInterface
{
public function notify(object $sender, string $event, mixed $data = null): void;
}
1
2
3
4
5
6
7
8
<?php

namespace App\Contracts;

interface ColleagueInterface
{
public function setMediator(MediatorInterface $mediator): void;
}

聊天室中介者

聊天中介者接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

namespace App\Contracts\Chat;

interface ChatMediatorInterface
{
public function addUser(User $user): void;

public function removeUser(User $user): void;

public function sendMessage(string $message, User $sender, ?string $recipient = null): void;

public function broadcast(string $message, User $sender): void;
}

聊天室实现

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
<?php

namespace App\Mediators\Chat;

use App\Contracts\Chat\ChatMediatorInterface;
use App\Models\User;
use Illuminate\Support\Collection;

class ChatRoom implements ChatMediatorInterface
{
protected Collection $users;
protected string $name;

public function __construct(string $name)
{
$this->name = $name;
$this->users = collect();
}

public function addUser(User $user): void
{
if (!$this->users->contains('id', $user->id)) {
$this->users->push($user);
$this->broadcast("{$user->name} has joined the chat", $user);
}
}

public function removeUser(User $user): void
{
$this->users = $this->users->reject(fn($u) => $u->id === $user->id);
$this->broadcast("{$user->name} has left the chat", $user);
}

public function sendMessage(string $message, User $sender, ?string $recipient = null): void
{
if ($recipient) {
$targetUser = $this->users->firstWhere('name', $recipient);

if ($targetUser) {
$targetUser->receivePrivateMessage($message, $sender);
}

return;
}

$this->broadcast($message, $sender);
}

public function broadcast(string $message, User $sender): void
{
foreach ($this->users as $user) {
if ($user->id !== $sender->id) {
$user->receiveMessage($message, $sender);
}
}
}

public function getUsers(): Collection
{
return $this->users;
}

public function getName(): string
{
return $this->name;
}
}

用户类

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
<?php

namespace App\Models;

use App\Contracts\Chat\ChatMediatorInterface;

class User
{
protected ?ChatMediatorInterface $mediator = null;
public int $id;
public string $name;

public function __construct(int $id, string $name)
{
$this->id = $id;
$this->name = $name;
}

public function setMediator(ChatMediatorInterface $mediator): void
{
$this->mediator = $mediator;
}

public function send(string $message, ?string $recipient = null): void
{
if ($this->mediator) {
$this->mediator->sendMessage($message, $this, $recipient);
}
}

public function receiveMessage(string $message, User $sender): void
{
echo "[{$this->name}] Received from {$sender->name}: {$message}\n";
}

public function receivePrivateMessage(string $message, User $sender): void
{
echo "[{$this->name}] Private from {$sender->name}: {$message}\n";
}

public function join(ChatMediatorInterface $chatRoom): void
{
$this->mediator = $chatRoom;
$chatRoom->addUser($this);
}

public function leave(): void
{
if ($this->mediator) {
$this->mediator->removeUser($this);
$this->mediator = null;
}
}
}

表单组件中介者

表单中介者接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

namespace App\Contracts\Form;

interface FormMediatorInterface
{
public function notify(object $component, string $event, mixed $data = null): void;

public function registerComponent(string $name, FormComponentInterface $component): void;

public function getComponent(string $name): ?FormComponentInterface;

public function validate(): bool;

public function getData(): array;
}

表单组件接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

namespace App\Contracts\Form;

interface FormComponentInterface
{
public function setMediator(FormMediatorInterface $mediator): void;

public function getName(): string;

public function getValue(): mixed;

public function setValue(mixed $value): void;

public function validate(): array;

public function render(): string;
}

表单中介者实现

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
<?php

namespace App\Mediators\Form;

use App\Contracts\Form\FormMediatorInterface;
use App\Contracts\Form\FormComponentInterface;
use Illuminate\Support\Collection;

class FormMediator implements FormMediatorInterface
{
protected Collection $components;
protected array $errors = [];

public function __construct()
{
$this->components = collect();
}

public function registerComponent(string $name, FormComponentInterface $component): void
{
$component->setMediator($this);
$this->components->put($name, $component);
}

public function getComponent(string $name): ?FormComponentInterface
{
return $this->components->get($name);
}

public function notify(object $component, string $event, mixed $data = null): void
{
$componentName = $component->getName();

switch ($event) {
case 'change':
$this->handleComponentChange($componentName, $data);
break;
case 'focus':
$this->handleComponentFocus($componentName);
break;
case 'blur':
$this->handleComponentBlur($componentName);
break;
case 'error':
$this->errors[$componentName] = $data;
break;
}
}

public function validate(): bool
{
$this->errors = [];
$isValid = true;

foreach ($this->components as $component) {
$errors = $component->validate();

if (!empty($errors)) {
$this->errors[$component->getName()] = $errors;
$isValid = false;
}
}

return $isValid;
}

public function getData(): array
{
$data = [];

foreach ($this->components as $name => $component) {
$data[$name] = $component->getValue();
}

return $data;
}

public function getErrors(): array
{
return $this->errors;
}

protected function handleComponentChange(string $name, mixed $value): void
{
unset($this->errors[$name]);

if ($name === 'country') {
$this->updateStates($value);
}

if ($name === 'zipCode') {
$this->validateZipCode($value);
}
}

protected function handleComponentFocus(string $name): void
{
}

protected function handleComponentBlur(string $name): void
{
$component = $this->getComponent($name);

if ($component) {
$errors = $component->validate();

if (!empty($errors)) {
$this->errors[$name] = $errors;
}
}
}

protected function updateStates(string $country): void
{
$stateComponent = $this->getComponent('state');

if ($stateComponent) {
$states = $this->getStatesForCountry($country);
$stateComponent->setOptions($states);
}
}

protected function validateZipCode(string $zipCode): void
{
}

protected function getStatesForCountry(string $country): array
{
return config("states.{$country}", []);
}
}

UI 组件中介者

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
<?php

namespace App\Mediators\UI;

use App\Contracts\MediatorInterface;

class UIMediator implements MediatorInterface
{
protected $button;
protected $textbox;
protected $checkbox;
protected $listbox;

public function setButton($button): void
{
$this->button = $button;
}

public function setTextbox($textbox): void
{
$this->textbox = $textbox;
}

public function setCheckbox($checkbox): void
{
$this->checkbox = $checkbox;
}

public function setListbox($listbox): void
{
$this->listbox = $listbox;
}

public function notify(object $sender, string $event, mixed $data = null): void
{
if ($sender === $this->checkbox) {
$this->handleCheckboxEvent($event, $data);
}

if ($sender === $this->textbox) {
$this->handleTextboxEvent($event, $data);
}

if ($sender === $this->listbox) {
$this->handleListboxEvent($event, $data);
}
}

protected function handleCheckboxEvent(string $event, mixed $data): void
{
if ($event === 'toggle') {
$this->textbox->setEnabled($data);
$this->button->setEnabled($data);
}
}

protected function handleTextboxEvent(string $event, mixed $data): void
{
if ($event === 'change') {
$this->button->setEnabled(!empty($data));
}
}

protected function handleListboxEvent(string $event, mixed $data): void
{
if ($event === 'select') {
$this->textbox->setValue($data);
}
}
}

事件总线中介者

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
<?php

namespace App\Mediators\Events;

use Illuminate\Support\Collection;

class EventBus
{
protected Collection $listeners;
protected Collection $wildcardListeners;

public function __construct()
{
$this->listeners = collect();
$this->wildcardListeners = collect();
}

public function subscribe(string $event, callable $handler): void
{
if ($event === '*') {
$this->wildcardListeners->push($handler);
} else {
if (!$this->listeners->has($event)) {
$this->listeners->put($event, collect());
}

$this->listeners->get($event)->push($handler);
}
}

public function unsubscribe(string $event, callable $handler): void
{
if ($event === '*') {
$this->wildcardListeners = $this->wildcardListeners->reject(fn($h) => $h === $handler);
} elseif ($this->listeners->has($event)) {
$this->listeners->put(
$event,
$this->listeners->get($event)->reject(fn($h) => $h === $handler)
);
}
}

public function publish(string $event, mixed $data = null): void
{
if ($this->listeners->has($event)) {
foreach ($this->listeners->get($event) as $handler) {
$handler($event, $data);
}
}

foreach ($this->wildcardListeners as $handler) {
$handler($event, $data);
}
}

public function hasListeners(string $event): bool
{
return $this->listeners->has($event) || $this->wildcardListeners->isNotEmpty();
}
}

订单处理中介者

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
<?php

namespace App\Mediators\Order;

use App\Contracts\MediatorInterface;
use App\Services\InventoryService;
use App\Services\PaymentService;
use App\Services\ShippingService;
use App\Services\NotificationService;

class OrderMediator implements MediatorInterface
{
protected InventoryService $inventory;
protected PaymentService $payment;
protected ShippingService $shipping;
protected NotificationService $notification;

public function __construct(
InventoryService $inventory,
PaymentService $payment,
ShippingService $shipping,
NotificationService $notification
) {
$this->inventory = $inventory;
$this->payment = $payment;
$this->shipping = $shipping;
$this->notification = $notification;
}

public function notify(object $sender, string $event, mixed $data = null): void
{
switch ($event) {
case 'order.created':
$this->handleOrderCreated($data);
break;
case 'payment.completed':
$this->handlePaymentCompleted($data);
break;
case 'payment.failed':
$this->handlePaymentFailed($data);
break;
case 'inventory.reserved':
$this->handleInventoryReserved($data);
break;
case 'inventory.insufficient':
$this->handleInventoryInsufficient($data);
break;
case 'shipping.created':
$this->handleShippingCreated($data);
break;
case 'order.cancelled':
$this->handleOrderCancelled($data);
break;
}
}

protected function handleOrderCreated($order): void
{
$this->inventory->reserve($order, $this);
$this->notification->send(
$order->user,
'Order Created',
"Your order #{$order->id} has been created."
);
}

protected function handlePaymentCompleted($data): void
{
$order = $data['order'];
$this->shipping->createShipment($order, $this);
$this->notification->send(
$order->user,
'Payment Successful',
"Payment for order #{$order->id} has been processed."
);
}

protected function handlePaymentFailed($data): void
{
$order = $data['order'];
$this->inventory->release($order);
$this->notification->send(
$order->user,
'Payment Failed',
"Payment for order #{$order->id} has failed."
);
}

protected function handleInventoryReserved($data): void
{
$order = $data['order'];
$this->payment->process($order, $this);
}

protected function handleInventoryInsufficient($data): void
{
$order = $data['order'];
$order->update(['status' => 'cancelled']);
$this->notification->send(
$order->user,
'Order Cancelled',
"Order #{$order->id} has been cancelled due to insufficient inventory."
);
}

protected function handleShippingCreated($data): void
{
$order = $data['order'];
$trackingNumber = $data['tracking_number'];

$order->update([
'status' => 'shipped',
'tracking_number' => $trackingNumber,
]);

$this->notification->send(
$order->user,
'Order Shipped',
"Your order #{$order->id} has been shipped. Tracking: {$trackingNumber}"
);
}

protected function handleOrderCancelled($data): void
{
$order = $data['order'];
$this->inventory->release($order);

if ($order->payment_status === 'completed') {
$this->payment->refund($order);
}

$this->notification->send(
$order->user,
'Order Cancelled',
"Your order #{$order->id} has been cancelled."
);
}
}

测试中介者模式

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
<?php

namespace Tests\Unit\Mediators;

use Tests\TestCase;
use App\Mediators\Chat\ChatRoom;
use App\Models\User;

class MediatorTest extends TestCase
{
public function test_chat_room_mediator(): void
{
$chatRoom = new ChatRoom('General');

$user1 = new User(1, 'Alice');
$user2 = new User(2, 'Bob');
$user3 = new User(3, 'Charlie');

$user1->join($chatRoom);
$user2->join($chatRoom);
$user3->join($chatRoom);

$this->assertCount(3, $chatRoom->getUsers());

$user1->send('Hello everyone!');

$user1->leave();

$this->assertCount(2, $chatRoom->getUsers());
}

public function test_private_message(): void
{
$chatRoom = new ChatRoom('Private');

$user1 = new User(1, 'Alice');
$user2 = new User(2, 'Bob');

$user1->join($chatRoom);
$user2->join($chatRoom);

$user1->send('Hello Bob!', 'Bob');
}
}

最佳实践

1. 中介者应该专注

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

class FocusedMediator implements MediatorInterface
{
public function notify(object $sender, string $event, mixed $data = null): void
{
$this->handleEvent($event, $data);
}

protected function handleEvent(string $event, mixed $data): void
{
}
}

2. 使用事件映射

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

class EventMappedMediator implements MediatorInterface
{
protected array $eventHandlers = [];

public function __construct()
{
$this->eventHandlers = [
'user.created' => [$this, 'handleUserCreated'],
'user.deleted' => [$this, 'handleUserDeleted'],
];
}

public function notify(object $sender, string $event, mixed $data = null): void
{
if (isset($this->eventHandlers[$event])) {
call_user_func($this->eventHandlers[$event], $data);
}
}
}

3. 避免中介者过于庞大

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
<?php

class ModularMediator implements MediatorInterface
{
protected array $modules = [];

public function registerModule(string $name, MediatorInterface $module): void
{
$this->modules[$name] = $module;
}

public function notify(object $sender, string $event, mixed $data = null): void
{
[$module, $subEvent] = $this->parseEvent($event);

if (isset($this->modules[$module])) {
$this->modules[$module]->notify($sender, $subEvent, $data);
}
}

protected function parseEvent(string $event): array
{
$parts = explode('.', $event, 2);
return [$parts[0], $parts[1] ?? ''];
}
}

总结

Laravel 13 的中介者模式提供了一种有效的方式来降低对象间的耦合。通过合理使用中介者模式,可以创建更加模块化、可维护的系统架构,同时保持组件间的通信清晰有序。