پترن مدیتور چیست و کاربرد آن در چه مواردی است؟
پترن مدیتور جزء پترن های رفتاری (Behavioral Design Pattern) می باشد پترن های رفتاری برای ارتباط بین اجزا و هر چه منعطف بودن این ارتباط و ایجاد یک وابستگی سست بین اجزا به کار می روند و در رابطه با پترن مدیتور همانطور که از معنی لغوی آن مشخص است به عنوان یک واسط برای ارتباط بین اجزا استفاده می شود و هدفی که دنبال می کند مثل سایر پترن های رفتاری کم کردن وابستگی و پیچیدگی می باشد و ارتباط بین اجزا نه از چند جا بلکه از یک نقطه فراهم می شود و به جای ارتباط سخت بین اجزا ارتباط غیر مستقیم از طریق واسط را برای ما فراهم می کند.
تصویر زیر حالتی است که ما از مدیتور استفاده نمی کنیم و زنجیر وابستگی را ایجاد می کنیم
و تصویر دوم مربوط به حالتی است که از مدیتور استفاده کرده باشیم
در رابطه با موارد استفاده از مدیتور:
در کل پترن مدیتور برای شرایطی کاربرد دارد که ما وابسطگی ها ی زیادی بین کلاس هایمان داریم و به اصطلاح ماکارونی(spaghetti code) درست کرده ایم
پس زمانی ما از مدیتور استفاده می کنیم که :
-می خواهیم به تعداد زیادی وابستگی بین کلاس ها دسترسی داشته باشیم.
-زمانی که می خواهیم کلاس هایمان را اینکپسوله(encapsulate) کنیم و آنها از یکدیگر اطلاعی نداشته باشند و وابستگی های خود را از طریق مدیتور دریافت کنند.
-زمانی که بخواهیم یک اینترفیس داشته باشیم که همه کلاس های ما با آن آرتباط داشته باشند.
اکنون سوالی که پیش می آید این هست که چه نیازی به این پترن هست؟در چه شرایطی به این پترن نیاز داریم؟
فرض کنید چند کلاس را داریم که به صورت سفت و سخت از هم استفاده می کنند و به هم وابسطه هستند اگر یک نگاه کلی به آنها بیاندازیم می بینیم که ما یک زنجیر از وابسطگی را داریم که از جنس وابستگی سخت(tightly-coupled) هستند. حالا ممکن است بپرسید خب ایراد این کار چیست؟چیزی که واضح است، این موضوع هست که ساختاری با این شکل در آینده نه چندان دور مشکلات فراوانی را برای توسعه دهنده و نرم افزار ایجاد خواهد کردد و این مشکلات عبارتند از:
-در طول زمان تعداد کلاس ها و آبجک های ما بیشتر خواهند شد و این زنجیر همواره طولانی و طولانی تر خواهد شد و به کاهش انعطاف سیستم منجر خواهد شد.
-مدیریت این ارتباط ها به تدریج سخت تر می شود.
-هزینه نگهداری و زمان رفع مشکلات در آن آفزایش خواهد یافت.
-تغییراتی که برای بهبود نرم افزار برنامه ریزی شده باعث بالا رفتن ریسک خرابی نرم افزار خواهد شد.
طبیعتا چنین ساختار و چنین مشغله ای باب میل هیچ کدام از ما نیست.
پترن مدیتور برای این مشکل راهکاری ارائه می دهد و ببینیم راهکار مدیتور برای این مشکل چیست؟
پترن مدیتور به عنوان راهکار در مرکز ارتباط یک واسط را قرار می دهد و تمام ارتباط از طریق این واسط تحقق خواهد یافت به این صورت ارتباط سخت بین اجزا و کلاس ها به یک ارتباط سست (loosely-coupled) تبدیل خواهد شد . هیچ کلاسی برای ارتباط با کلاس دیگر نیازی به نگهداری رفرنس آن کلاس در بدنه خود ندارد و هیچ ارتباط مستقیمی وجود ندارد و ارتباط از طریق واسط انجام می شود.
یکی از مهمترین اهداف معماران نرم افزار این هست که اجزای سیستم در ارتباط سفت و سخت نباشند و در زمان لزوم بتوان به راحتی اجزا را با کمترین هزینه تغییر داد و یک سیستم کاملا منعطف را ایجاد کرد. از این رو مدیتور ارتباط سفت و سخت بین اجزای سیستم را از بین برده و هدف ذکر شده را به ارمغان می آورد.هدف پایه از این پترن به جای وابستگی سفت و سخت بین اجزا ،وابسطگی به یک واسط و ارتباط از طریق آن است.
مزایای استفاده از مدیتور چیست؟
خلاصه مطالب بالا، میدتور برای ایزوله کردن اجزا از هم دیگر به کار می رود و وابستگی بین اجزا و هزینه نگهداری سیستم را کاهش می دهد.
-وابستگی بین کلاس ها را کاهش می دهد.
-اصل تک وظیفگی را برای ما ایجاد می کند برای مثال کنترلر های شما نیازی به داشتن اطلاعات از سرویس های لایه پایین تر ندارند و کدی به مراتب تمیز تر در کنترلر های شما وجود دارد و کنترلر ها بر وظیفه اصلی خود متمرکز خواهند بود.
-اصل اوپن/کلوز(Open/Close) را برای ما ایجاد می کند به این صورت که لزوما تغییراتی که در لایه پایین تر اتفاق می افتد باعث ایجاد تغییرات در لایه بالاتر نخواهد شد .
معایب مدیتور
مثال اول - ایجاد نرم افزار چت با استفاده از پترن مدیتور:
فرض کنید تصمیم داریم یک اتاق صحبت مثل چیزی که در یاهو مسنجر در سالهای قبل شاهدش بودیم بسازیم. برای ارتباط شخصی با دیگر اشخاص ابتدا شخص وارد اتاق می شد و با اشخاص موجود در کلاس به صحبت می پرداخت. مشکلی که در اینجا وجود دارد این هست که هر کاربر اگر با سایر کاربران به صورت چند به چند(many-to-many) ارتباط برقرار کند پیچیدگی حاصل به شکل نمایی افزایش خواهد یافت . به همین علت اگر هر کاربر متن دلخواه خود را به یک نقطه ارسال می کند و پیام جدید را از آن نقطه دریافت کند، این باعث سادگی و مدیریت راحت تر سیستم می شود بیایید با مدیتور این مشکل را حل کنیم.
هدف ما این است که کاربران به جای چت مستقیم با یکدیگر پیام را به اتاق ارسال و پیام های رسیده را از آنجا دریافت کنند . به این شکل دسترسی مستقیم هر کاربر به سایرین از میان برداشته شده و فقط کافیست پیام خود را به اتاق تحویل دهند.
سیستممان به شکل زیر است:
کلاس پارتیسیپنت (Participant) اطلاعات کاربران را نگهداری می کند
کلاس چت روم (ChatRoom) مدیریت اتاق چت را بر عهده دارد.
public class Participant
{
public ChatRoom ChatRoom { get; set; }
public string Name { get; }
public Participant(string name)
{
Name = name;
}
public void Send(string receiver, string message)
{
ChatRoom.Send(Name, receiver, message);
}
public void Receive(string sender, string message)
{
Console.WriteLine($"{sender} to {Name}: '{message}'");
}
}
متد سند(Send) برای ارسال و متد رسیو(Receive) برای دریافت پیام استفاده می شود .
ابتدا اینترفیس کلاسمان را پیاده سازی می کنیم
public interface IChatRoom
{
public void Register(Participant participant);
public void Send(string sender, string receiver, string message);
}
و بعد کلاس چت روم که از این اینترفیس ارث بری می کند:
public class ChatRoom : IChatRoom
{
private Dictionary<string, Participant> _participants = new Dictionary<string, Participant>();
public void Register(Participant participant)
{
if (!_participants.ContainsValue(participant))
{
_participants[participant.Name] = participant;
}
participant.ChatRoom = this;
}
public void Send(string sender, string receiver, string message)
{
Participant participant = _participants[receiver];
if (participant != null)
{
participant.Receive(sender, message);
}
}
}
کلاس چت روم (ChatRoom) لیستی از پارتیسپنت ها را داخل خود نگهداری میکند و برای این کار برای هر کاربر وارد شده متد رجیستر(Register) فراخوانی می شود و اگر پیامی برای ارسال موجود باشد متد سند(Send) فراخوانی شده و پارتیسیپنت مربوطه از لیست واکشی شده و متد رسیو(Receive) پارتیسیپنت(Participant) فراخوانی شده و به کاربر اطلاع رسانی می شود.
اکنون تست نرم افزارمان را انجام دهیم.
ChatRoom chatroom = new ChatRoom();
Participant mohammad = new Participant("Mohammad");
Participant sara = new Participant("Sara");
Participant ali = new Participant("Ali");
chatroom.Register(mohammad);
chatroom.Register(sara);
chatroom.Register(ali);
mohammad.Send("Sara", "Hi, How are you?");
sara.Send("Mohammad", "Hi,I'm great.What about you?");
ali.Send("Sara", "Did you compelete your work yesterday?");
sara.Send("Ali", "Of course.");
کد های مثال فوق را می توانید از صفحه گیتهاب من دریافت کنید.
این مثال ساده ای از نحوه کار مدیتور بود در دات نت کتابخانه مدیت آر (MediatR) پیاده سازی پترن مدیتور را برای ما به ساده کرده است. بیایید با کتابخانه مدیت آر (MediatR) یک پروژه سمپل را پیاده سازی کنیم . در این پروژه تصمیم داریم یک وب ای پی آی (WebApi) را پیاده سازی کنیم و چند عملیات ساده ریاضی را با آن انجام دهیم.
ابتدا یک کنترلر وب ای پی آی (WebApi) ایجاد کرده و سپس کتابخانه مدیت آر(MediatR) را از ناگت بر روی پروژه سوار می کنیم بعد از آن کلاس عمل ضرب را در آن پیاده سازی می کنیم
public class MultiplyRequest:IRequest<int>
{
public int FirstNumber { get; set; }
public int SecondNumber { get; set; }
}
کلاس ما از اینترفیس آی ریکوئست (IRequest) مدیت آر (MediatR) ارث بری میکند و مقداری که بر خواهد گرداند یک مقدار عدد صحیح است . خب بیاید کلاس هندلر رکوئستمان را پیاده سازی کنیم .کلاس هندلر رکوئست درخواست شده را دریافت کرده و عملیات مربوط به آن را انجام داده و نتیجه را به درخواست کننده بر می گرداند .
public class MultiplyRequestHandler : IRequestHandler<MultiplyRequest, int>
{
public async Task<int> Handle(MultiplyRequest request, CancellationToken cancellationToken)
{
return request.FirstNumber * request.SecondNumber;
}
}
بعد از اتمام کد های کلاس هندلر اینک به پیاده سازی بدنه کنترلرمان می پردازیم ابتدا داخل کنترلر مدیتور را از طریق دپندنسی اینجکشن(Dependency Injection) دریافت کرده و اکشن های لازم را با استفاده از مدیتور پیاده سازی می کنیم
[Route("api/[controller]")]
[ApiController]
public class CalculatorsController : ControllerBase
{
private readonly IMediator _mediator;
public CalculatorsController(IMediator mediator) => _mediator = mediator;
[HttpGet(template:"multiply")]
public async Task<ActionResult> Multiply(int firstNumber, int secondNumber)
{
return Ok(await _mediator.Send(new MultiplyRequest() { FirstNumber = firstNumber, SecondNumber = secondNumber }));
}
[HttpGet(template: "divide")]
public async Task<ActionResult> Divide(int firstNumber, int secondNumber)
{
return Ok(await _mediator.Send(new DevideRequest() { FirstNumber = firstNumber, SecondNumber = secondNumber }));
}
}
کد های این مثال را نیز می توانید از صفحه گیتهاب من دریافت کنید.
همانطور که مشخص است شما بدون وابستگی سفت و سخت به سرویس های مربوط به عملیات های مختلف، عملیات را با استفاده از مدیتور انجام دادید.
قبل از به پایان رساندن مقاله یاد آوری کنم که مدیتور معمولا با پترن سی کیو آر اس (CQRS) استفاده می شود .پترن سی کیو آر اس (CQRS) برای جدا سازی عملیات خواندن و نوشتن در دیتابیس کاربرد دارد
امیدوارم مطالب نوشته شده مفید واقع شود.