در پروژههای PHP معمولاً چند کلاس مختلف باید با یکدیگر ارتباط داشته باشند.
برای مثال، در یک فروشگاه اینترنتی ممکن است کلاسهای زیر وجود داشته باشند:
OrderServicePaymentServiceInventoryServiceNotificationServiceUserServiceInvoiceService
در ابتدا شاید هر کلاس مستقیماً متدهای کلاسهای دیگر را فراخوانی کند؛ اما با بزرگتر شدن پروژه، ارتباطها پیچیده میشوند.
مثلاً:
OrderService
├── PaymentService
├── InventoryService
├── NotificationService
└── InvoiceService
از طرف دیگر، PaymentService نیز ممکن است به OrderService و NotificationService وابسته باشد.
در نتیجه، وابستگیها بهصورت شبکهای شکل میگیرند:
OrderService ↔ PaymentService
OrderService ↔ InventoryService
PaymentService ↔ NotificationService
InventoryService ↔ NotificationService
این وضعیت باعث میشود تغییر یک کلاس، روی چند کلاس دیگر اثر بگذارد.
Mediator Pattern برای حل چنین مشکلی استفاده میشود. در این الگو، ارتباط میان Objectها در یک Object مرکزی به نام Mediator قرار میگیرد و کلاسهای دیگر که به آنها Colleague گفته میشود، بهجای ارتباط مستقیم با یکدیگر، با Mediator ارتباط برقرار میکنند. citeturn0search0
Mediator Pattern چیست؟
Mediator Pattern یا الگوی واسط یکی از الگوهای طراحی رفتاری در برنامهنویسی شیگرا است.
هدف اصلی این Pattern، کاهش وابستگی مستقیم میان چند Object و متمرکز کردن منطق ارتباط آنها در یک Mediator است.
بهجای اینکه هر Object تمام Objectهای دیگر را بشناسد، فقط Mediator را میشناسد.
ساختار کلی:
Colleague A
│
▼
Mediator
▲
│
Colleague B
اگر تعداد Componentها بیشتر شود:
User Interface
│
▼
Mediator
┌───┼────┐
▼ ▼ ▼
User Order Notification
در این ساختار، User لازم نیست مستقیماً با Order یا Notification ارتباط داشته باشد.
تمام هماهنگیها در Mediator انجام میشود.
چه مشکلی را حل میکند؟
فرض کنید یک فرم ثبت سفارش داریم.
بعد از ثبت سفارش باید چند عملیات انجام شود:
- بررسی اطلاعات کاربر
- بررسی موجودی محصول
- پرداخت
- ایجاد فاکتور
- ارسال پیامک
- ارسال ایمیل
- ثبت گزارش
در پیادهسازی مستقیم ممکن است کلاس سفارش به تمام سرویسهای دیگر وابسته شود:
final class OrderService
{
public function __construct(
private PaymentService $paymentService,
private InventoryService $inventoryService,
private InvoiceService $invoiceService,
private NotificationService $notificationService,
) {}
public function create(): void
{
$this->inventoryService->reserve();
$this->paymentService->pay();
$this->invoiceService->create();
$this->notificationService->send();
}
}
این کد لزوماً اشتباه نیست، اما با پیچیدهتر شدن فرآیند، OrderService به نقطهای تبدیل میشود که همه کلاسها را میشناسد.
اگر منطق ارتباط بین سرویسها در چند نقطه دیگر نیز تکرار شود، نگهداری سیستم سختتر خواهد شد.
Mediator کمک میکند این هماهنگی در یک کلاس مشخص قرار بگیرد.
# اجزای اصلی Mediator Pattern
Mediator Pattern معمولاً از اجزای زیر تشکیل میشود.
1. Mediator Interface
قرارداد Mediator را مشخص میکند.
این Interface تعیین میکند Colleagueها از چه عملیاتهایی میتوانند استفاده کنند.
2. Concrete Mediator
پیادهسازی واقعی Mediator است.
این کلاس منطق هماهنگی میان Componentها را مدیریت میکند.
3. Colleague
هر Object یا Componentی که با Mediator همکاری میکند، Colleague نام دارد.
برای مثال:
UserInterface
UserRepository
PaymentService
OrderService
4. Client
Client معمولاً Mediator و Colleagueها را ایجاد و به یکدیگر متصل میکند.
# مثال ساده؛ ارتباط رابط کاربری و Repository
فرض کنید یک رابط کاربری داریم که میخواهد اطلاعات کاربر را نمایش دهد.
در روش مستقیم، UI باید مستقیماً Repository را بشناسد:
final class UserUi
{
public function __construct(
private UserRepository $repository
) {}
public function show(string $username): void
{
echo $this->repository->findName($username);
}
}
اگر بعدها لازم باشد قبل از نمایش اطلاعات:
- دسترسی کاربر بررسی شود.
- لاگ ثبت شود.
- اطلاعات Cache بررسی شود.
- پیام خاصی نمایش داده شود.
کلاس UI بهمرور مسئولیتهای بیشتری پیدا میکند.
در Mediator، UI فقط Mediator را میشناسد.
# ایجاد Mediator Interface
ابتدا Interface را ایجاد میکنیم:
interface MediatorInterface
{
public function getUserName(string $username): string;
}
در این مثال، Mediator متدی برای دریافت نام کاربر دارد.
# ایجاد کلاس پایه Colleague
برای اینکه تمام Colleagueها بتوانند Mediator را دریافت کنند، یک کلاس پایه ایجاد میکنیم:
abstract class Colleague
{
protected MediatorInterface $mediator;
public function setMediator(
MediatorInterface $mediator
): void {
$this->mediator = $mediator;
}
}
حالا هر کلاس فرزند میتواند به Mediator دسترسی داشته باشد.
# ایجاد UserRepository
final class UserRepository extends Colleague
{
public function findName(string $username): string
{
return "User: {$username}";
}
}
در این مثال، Repository فقط وظیفه دریافت اطلاعات کاربر را دارد.
# ایجاد UserUi
final class UserUi extends Colleague
{
public function showUser(
string $username
): void {
echo $this->mediator->getUserName($username);
}
}
نکته مهم این است که UserUi مستقیماً UserRepository را نمیشناسد.
فقط به Mediator متصل است:
$this->mediator->getUserName($username);
# ایجاد Concrete Mediator
حالا پیادهسازی واقعی Mediator را ایجاد میکنیم:
final class UserMediator implements MediatorInterface
{
public function __construct(
private UserRepository $repository,
private UserUi $ui,
) {
$this->repository->setMediator($this);
$this->ui->setMediator($this);
}
public function getUserName(
string $username
): string {
return $this->repository->findName($username);
}
public function showUser(
string $username
): void {
$this->ui->showUser($username);
}
}
در اینجا Mediator دو Component را مدیریت میکند:
UserRepository
UserUi
و ارتباط میان آنها را کنترل میکند.
# استفاده از Mediator
$repository = new UserRepository();
$ui = new UserUi();
$mediator = new UserMediator(
repository: $repository,
ui: $ui,
);
$mediator->showUser('Saeed');
خروجی:
User: Saeed
روند اجرای درخواست:
Client
↓
UserMediator
↓
UserUi
↓
UserMediator
↓
UserRepository
در این ساختار، UI و Repository بهصورت مستقیم به یکدیگر وابسته نیستند.
# مثال کاربردیتر؛ ثبت سفارش
حالا یک مثال واقعیتر ایجاد کنیم.
فرض کنید هنگام ثبت سفارش باید سه سرویس با یکدیگر همکاری کنند:
PaymentServiceInventoryServiceNotificationService
بهجای اینکه هر سرویس، سرویسهای دیگر را مستقیماً فراخوانی کند، یک OrderMediator ایجاد میکنیم.
تعریف Mediator
interface OrderMediatorInterface
{
public function processPayment(
int $orderId,
int $amount
): void;
public function reserveInventory(
int $orderId
): void;
public function sendNotification(
int $orderId
): void;
}
ایجاد PaymentService
final class PaymentService
{
public function pay(
int $orderId,
int $amount
): void {
echo "Payment completed for order {$orderId}.\n";
}
}
ایجاد InventoryService
final class InventoryService
{
public function reserve(
int $orderId
): void {
echo "Inventory reserved for order {$orderId}.\n";
}
}
ایجاد NotificationService
final class NotificationService
{
public function send(
int $orderId
): void {
echo "Notification sent for order {$orderId}.\n";
}
}
ایجاد Concrete Mediator
final class OrderMediator
implements OrderMediatorInterface
{
public function __construct(
private PaymentService $paymentService,
private InventoryService $inventoryService,
private NotificationService $notificationService,
) {}
public function processPayment(
int $orderId,
int $amount
): void {
$this->paymentService->pay(
orderId: $orderId,
amount: $amount,
);
}
public function reserveInventory(
int $orderId
): void {
$this->inventoryService->reserve($orderId);
}
public function sendNotification(
int $orderId
): void {
$this->notificationService->send($orderId);
}
}
در اینجا Mediator مسئول هماهنگی سرویسهاست.
# ایجاد OrderService
final class OrderService
{
public function __construct(
private OrderMediatorInterface $mediator,
) {}
public function create(
int $orderId,
int $amount
): void {
$this->mediator->reserveInventory($orderId);
$this->mediator->processPayment(
orderId: $orderId,
amount: $amount,
);
$this->mediator->sendNotification($orderId);
}
}
حالا OrderService فقط Mediator را میشناسد.
دیگر نیازی ندارد مستقیماً به Payment، Inventory یا Notification وابسته باشد.
# اجرای مثال سفارش
$mediator = new OrderMediator(
paymentService: new PaymentService(),
inventoryService: new InventoryService(),
notificationService: new NotificationService(),
);
$orderService = new OrderService(
mediator: $mediator
);
$orderService->create(
orderId: 1001,
amount: 250000,
);
خروجی:
Inventory reserved for order 1001.
Payment completed for order 1001.
Notification sent for order 1001.
# مزیت اصلی Mediator چیست؟
مزیت اصلی Mediator این است که ارتباطهای پیچیده میان Componentها را متمرکز میکند.
بدون Mediator:
A ↔ B
A ↔ C
A ↔ D
B ↔ C
B ↔ D
C ↔ D
با Mediator:
A ─┐
B ─┤
C ─┼── Mediator
D ─┘
در این حالت، هر Component فقط به Mediator وابسته است.
این موضوع باعث کاهش Coupling یا وابستگی میان کلاسها میشود. citeturn0search5
# Mediator در Laravel
در Laravel میتوان از Mediator برای هماهنگی میان بخشهای مختلف یک فرآیند استفاده کرد.
برای مثال:
- ثبت سفارش
- تکمیل پرداخت
- ایجاد فاکتور
- ارسال اعلان
- بررسی موجودی
- هماهنگی سرویسهای خارجی
- اجرای Workflow
- مدیریت فرآیند ثبتنام
یک ساختار پیشنهادی:
app/
└── Domain/
└── Orders/
├── Contracts/
│ └── OrderMediatorInterface.php
│
├── Mediators/
│ └── OrderMediator.php
│
├── Services/
│ ├── PaymentService.php
│ ├── InventoryService.php
│ └── NotificationService.php
│
└── Actions/
└── CreateOrder.php
در این ساختار:
- Serviceها عملیات تخصصی خود را انجام میدهند.
- Mediator ارتباط میان Serviceها را مدیریت میکند.
- Action یا Application Service فرآیند اصلی را اجرا میکند.
# ثبت Mediator در Service Container
در Laravel میتوان Mediator را در Service Container ثبت کرد:
use App\Domain\Orders\Mediators\OrderMediator;
use App\Domain\Orders\Contracts\OrderMediatorInterface;
$this->app->bind(
OrderMediatorInterface::class,
OrderMediator::class
);
حالا Laravel میتواند هنگام نیاز، پیادهسازی مناسب را Inject کند.
مثلاً:
final class CreateOrder
{
public function __construct(
private OrderMediatorInterface $mediator
) {}
public function handle(): void
{
// اجرای فرآیند ثبت سفارش
}
}
این روش با Dependency Injection سازگار است و تست کردن کد را سادهتر میکند.
# آیا Laravel Event و Listener همان Mediator هستند؟
خیر، اما ممکن است از نظر هدف با یکدیگر همپوشانی داشته باشند.
در Event و Listener، یک Event منتشر میشود و Listenerهای مختلف به آن واکنش نشان میدهند:
OrderCreated
├── SendEmail
├── SendSms
└── UpdateStatistics
اما Mediator معمولاً منطق هماهنگی را در یک Object مرکزی مدیریت میکند.
تفاوت اصلی:
Mediator
= هماهنگی متمرکز میان چند Component
Event / Listener
= اطلاعرسانی و واکنش به یک رویداد
اگر لازم باشد ترتیب اجرا، شرایط اجرا و وابستگی میان عملیاتها بهصورت مشخص مدیریت شود، Mediator میتواند انتخاب مناسبی باشد.
# تفاوت Mediator و Facade
Mediator و Facade هر دو ممکن است یک نقطه ورود ساده برای کار با چند کلاس ایجاد کنند، اما هدف آنها متفاوت است.
Facade
Facade یک رابط ساده برای استفاده از یک Subsystem پیچیده فراهم میکند.
مثلاً:
$paymentFacade->payOrder();
کاربر Facade لازم نیست جزئیات سرویسهای داخلی را بداند.
Mediator
Mediator بیشتر روی ارتباط و هماهنگی میان چند Component تمرکز دارد.
در Mediator، خود Componentها نیز معمولاً Mediator را میشناسند و از طریق آن با یکدیگر ارتباط میگیرند.
بهصورت خلاصه:
Facade
= ساده کردن استفاده از یک سیستم
Mediator
= مدیریت ارتباط بین چند Object
# تفاوت Mediator و Observer
Observer برای اطلاعرسانی به چند Observer پس از رخ دادن یک تغییر استفاده میشود.
Mediator معمولاً یک «مرکز هماهنگی» دارد که تصمیم میگیرد Componentها چگونه با یکدیگر تعامل کنند.
در Observer:
Subject
↓
Observers
در Mediator:
Colleague A
↘
Mediator
↗
Colleague B
Mediator زمانی مناسبتر است که منطق تعامل پیچیده و دوطرفه باشد.
# تفاوت Mediator و Chain of Responsibility
در Chain of Responsibility، یک درخواست از میان زنجیرهای از Handlerها عبور میکند:
Handler A
↓
Handler B
↓
Handler C
اما در Mediator، چند Component از طریق یک Object مرکزی با یکدیگر ارتباط دارند:
Component A
↓
Mediator
↑
Component B
Chain بیشتر برای پردازش مرحلهای مناسب است.
Mediator بیشتر برای هماهنگی بین Componentهای مختلف استفاده میشود.
# مزایای Mediator Pattern در PHP
1. کاهش Coupling
کلاسها لازم نیست تمام Componentهای دیگر را بشناسند.
2. متمرکز شدن منطق ارتباط
منطق تعامل در یک نقطه قرار میگیرد.
3. سادهتر شدن تغییرات
تغییر نحوه ارتباط Componentها معمولاً در Mediator انجام میشود.
4. افزایش تستپذیری
میتوان Mediator را با Mock یا Fake تست کرد.
5. مناسب برای فرآیندهای پیچیده
برای ثبت سفارش، پرداخت، Workflow و هماهنگی سرویسها مفید است.
6. کاهش وابستگی مستقیم
هر Colleague فقط به Interface مربوط به Mediator وابسته میشود.
# معایب Mediator Pattern
1. خطر تبدیل شدن Mediator به God Object
اگر تمام منطق برنامه را داخل Mediator قرار دهیم، آن کلاس بیش از حد بزرگ میشود.
2. تمرکز بیش از حد مسئولیتها
Mediator باید هماهنگکننده باشد، نه اینکه تمام عملیات تخصصی را خودش انجام دهد.
3. دشوار شدن نگهداری Mediator بزرگ
اگر تعداد Componentها زیاد شود، ممکن است Mediator نیز پیچیده شود.
4. احتمال پنهان شدن وابستگیها
گاهی استفاده بیش از حد از Mediator باعث میشود وابستگیهای واقعی سیستم بهوضوح دیده نشوند.
# اشتباهات رایج در استفاده از Mediator
اشتباه اول: قرار دادن تمام Business Logic در Mediator
Mediator نباید جای تمام Serviceها را بگیرد.
بهتر است:
PaymentService
= مسئول پرداخت
InventoryService
= مسئول موجودی
Mediator
= مسئول هماهنگی
اشتباه دوم: ایجاد Mediator برای دو کلاس ساده
اگر فقط دو کلاس ارتباط بسیار سادهای دارند، شاید Dependency Injection مستقیم کافی باشد.
اشتباه سوم: وابستگی Concrete بهجای Interface
بهتر است Componentها به قرارداد Mediator وابسته باشند:
private MediatorInterface $mediator;
نه یک پیادهسازی خاص.
اشتباه چهارم: تبدیل Mediator به کلاس چند هزار خطی
اگر Mediator بیش از حد بزرگ شد، میتوان آن را به چند Mediator تخصصی تقسیم کرد.
# چه زمانی از Mediator Pattern استفاده کنیم؟
Mediator انتخاب مناسبی است وقتی:
- چند Object ارتباط پیچیدهای با یکدیگر دارند.
- وابستگیهای مستقیم زیاد شدهاند.
- تغییر یک Component روی چند Component دیگر اثر میگذارد.
- نیاز به یک مرکز هماهنگی وجود دارد.
- ترتیب یا شرایط تعاملها اهمیت دارد.
- میخواهیم Componentها قابل تست و مستقلتر باشند.
نمونههای واقعی:
Order Workflow
Payment Process
Chat Room
UI Components
Reservation System
Notification Coordination
Workflow Engine
Game Objects
Microservice Orchestration
# چه زمانی از Mediator استفاده نکنیم؟
اگر ارتباط میان کلاسها ساده و مستقیم است، استفاده از Mediator ممکن است فقط پیچیدگی اضافه کند.
برای مثال:
$userRepository->find($id);
نیازی به Mediator ندارد.
همچنین اگر تنها هدف شما ارائه یک API ساده برای چند سرویس است و سرویسها نیازی به شناخت Mediator ندارند، احتمالاً Facade انتخاب مناسبتری خواهد بود.
# تست Mediator با PHPUnit
برای تست Mediator میتوان از Mock استفاده کرد:
public function test_mediator_calls_payment_service(): void
{
$paymentService = $this->createMock(
PaymentService::class
);
$paymentService
->expects($this->once())
->method('pay')
->with(1001, 250000);
$mediator = new OrderMediator(
paymentService: $paymentService,
inventoryService: new InventoryService(),
notificationService: new NotificationService(),
);
$mediator->processPayment(
orderId: 1001,
amount: 250000,
);
}
در این تست بررسی میکنیم که Mediator متد pay() را با پارامترهای صحیح فراخوانی کند.
# Mediator و اصل Single Responsibility
Mediator نباید مسئول تمام عملیات باشد.
بهتر است مسئولیتها به این شکل تقسیم شوند:
PaymentService
→ پرداخت
InventoryService
→ رزرو موجودی
NotificationService
→ ارسال اعلان
OrderMediator
→ هماهنگی بین سرویسها
در این صورت هر کلاس وظیفه مشخصی دارد و تغییر در یک بخش، اثر کمتری روی بخشهای دیگر میگذارد.
# Mediator و Dependency Inversion
بهتر است Mediator به Interfaceهای سرویسها وابسته باشد:
interface PaymentServiceInterface
{
public function pay(
int $orderId,
int $amount
): void;
}
سپس Mediator به قرارداد وابسته شود:
final class OrderMediator
{
public function __construct(
private PaymentServiceInterface $paymentService,
) {}
}
این روش امکان استفاده از پیادهسازیهای مختلف و تست آسانتر را فراهم میکند.
# جمعبندی Mediator Pattern در PHP
Mediator Pattern یکی از الگوهای طراحی رفتاری است که برای کاهش وابستگی میان چند Object و متمرکز کردن منطق ارتباط آنها استفاده میشود.
در روش مستقیم، Componentها ممکن است به تعداد زیادی کلاس دیگر وابسته باشند:
A ↔ B
A ↔ C
B ↔ C
اما با Mediator:
A ─┐
B ─┼── Mediator
C ─┘
هر Component فقط Mediator را میشناسد و ارتباط میان آنها از طریق Mediator مدیریت میشود.
این Pattern برای سیستمهای ثبت سفارش، پرداخت، Workflow، رابطهای کاربری، اعلانها و فرآیندهای چندمرحلهای کاربرد دارد.
با این حال، نباید Mediator را به یک God Object تبدیل کرد. وظیفه Mediator باید هماهنگی باشد، نه اجرای تمام عملیات تخصصی سیستم.
# سوالات متداول درباره Mediator Pattern در PHP
Mediator Pattern در PHP چیست؟
Mediator Pattern الگویی رفتاری است که ارتباط میان چند Object را در یک Object مرکزی به نام Mediator مدیریت میکند و وابستگی مستقیم میان Componentها را کاهش میدهد.
Colleague در Mediator Pattern چیست؟
Colleague به Componentهایی گفته میشود که با Mediator همکاری میکنند. این Componentها بهجای ارتباط مستقیم با یکدیگر، از Mediator استفاده میکنند.
Mediator چه تفاوتی با Facade دارد؟
Facade یک رابط ساده برای استفاده از یک Subsystem فراهم میکند، اما Mediator ارتباط و هماهنگی میان چند Object را مدیریت میکند.
آیا Mediator در Laravel کاربرد دارد؟
بله. میتوان از آن برای هماهنگی سرویسهای سفارش، پرداخت، موجودی، اعلان، فاکتور و Workflow استفاده کرد.
آیا Event و Listener جایگزین Mediator هستند؟
در بعضی شرایط بله، اما هدف آنها متفاوت است. Event و Listener بیشتر برای اطلاعرسانی و واکنش به رویداد استفاده میشوند، در حالی که Mediator برای هماهنگی متمرکز تعاملها مناسب است.
آیا Mediator باعث حذف کامل وابستگی میشود؟
خیر. وابستگی حذف نمیشود، بلکه از ارتباط مستقیم میان Componentها به وابستگی به یک قرارداد مشترک منتقل میشود.
آیا استفاده از Mediator همیشه مناسب است؟
خیر. اگر ارتباط میان کلاسها ساده باشد، استفاده از Mediator ممکن است پیچیدگی غیرضروری ایجاد کند.
بزرگترین خطر Mediator چیست؟
بزرگترین خطر این است که Mediator به یک کلاس بسیار بزرگ تبدیل شود و تمام Business Logic سیستم را در خود جای دهد.





