یکی از اصولی که در طراحی شئ گرا باید رعایت شود این است که: کلاس ها و اشیائی که از روی آنها ساخته میشوند، باید حداقل وابستگی را به یکدیگر داشته باشند. به این معنی که هرکدام بتوانند به شکل جداگانه کار خود را انجام بدهند و در صورت نیاز فارغ از این که هرکدام چگونه وظیفه خود را انجام می دهند با یکدیگر تعامل داشته باشند. این تعامل باید به گونه ای باشد که وقتی تغییری در یکی از کلاس ها اعمال شد، کلاسی که با کلاس تغییر داده شده همکاری می کند نیازمند تغییر نباشد...
چنین اپلیکیشنی اصطلاحا Loosly Coupled هست و کامپوننت های اون کمترین وابستگی به هم رو دارند. یکی از الگوهایی که در رعایت این اصل بسیار تاثیر میگذاره، الگوی طراحی Mediator است.
الگوی Mediator یا میانجی یک الگوی طراحی رفتاری (Behavioral) است که به ما امکان می دهد وابستگی های آشفته بین اشیا را کاهش دهیم. این الگو ارتباطات مستقیم بین اشیا را محدود می کند و آنها را مجبور می کند که فقط از طریق یک شئ واسطه یا میانجی همکاری کنند.
فرض کنید که برای ساخت و ویرایش پروفایل کاربران، یک دیالوگ داریم.. که شامل کنترل های مختلف فرم مانند تکست اینپوت ، چک باکس ، دکمه ها و غیره است.
برخی از عناصر فرم ممکن است با بقیه تعامل داشته باشند. مثلا ممکنه با انتخاب یک چک باکس (مثلا "شاغل هستم")، یه تکست اینپوت جدید ظاهر بشه و داده جدیدی (عنوان شغل) رو بگیره.
با قرار دادن منطق به طور مستقیم در داخل کدهای عناصر فرم ، استفاده از کلاس های این عناصر در سایر فرمها برنامه بسیار دشوار میشود. برای مثال نمی توانید از آن کلاس چک باکس، داخل فرم دیگری استفاده کنید، چونکه مستقیما به فیلد "عنوان شغل" وصله.
برای طراحی یک فرم دیگر، یا باید از تمام کلاس ها کنار هم استفاده کنید و یا از هیچ کدام استفاده نکنید.
الگوی Mediator پیشنهاد می کند که شما تمام ارتباطات مستقیم را بین کلاس هایی که می خواهید مستقل از یکدیگر ایجاد کنید ، از بین ببرید... در عوض، این اجزا باید به طور غیر مستقیم و با فراخوانی یک شیء میانجی که تماس ها را به اجزای مناسب هدایت می کند، همکاری کنند. در نتیجه ، اجزاء به جای اینکه به ده ها تن از همکاران خود متکی باشند ، فقط به یک طبقه میانجی، وابستگی دارند.
در مثالی که زدیم خود دیالوگ ما میتواند به عنوان شئ میانجی عمل کند. این دیالوگ از تمام عناصر خود آگاه است، پس حتی نیازی نداریم وابستگی های جدیدی رو در این کلاس به وجود بیاریم.
مهمترین تغییر در عناصر اتفاق می افتد. بیایید دکمه ارسال را در نظر بگیریم. قبلاً ، هر بار که یک کاربر روی این دکمه کلیک می کرد، باید تمام مقادیر فرم اعتبار سنجی میشد. اما اکنون تنها وظیفه آن اطلاع رسانی به دیالوگ در مورد کلیک است. پس از دریافت این اعلان ، خود دیالوگ اعتبارسنجی ها را انجام می دهد یا کار را به عناصر دیگری منتقل می کند. بنابراین دکمه ارسال، به جای اینکه به تمام عناصر فرم گره خورده باشد، تنها به کلاس دیالوگ وابسته است.
حتی می توانید از این هم فراتر رفته و با استخراج اینترفیس های مشترک برای تمام دیالوگ ها، وابستگی ها را بیشتر هم کاهش دهید. این اینترفیس یک متد اعلان را اعمال کند، که همه عناصر فرم می توانند از آن برای اطلاع رسانی به دیالوگ در مورد رویدادها استفاده کنند. بنابراین دکمه ارسال ما باید بتواند با هر دیالوگی که از اینترفیس تبعیت میکند، کار کند.
به این ترتیب، الگوی Mediator به شما امکان می دهد یک شبکه پیچیده از روابط بین اشیاء مختلف را در داخل یک شیء میانجی واحد قرار دهید. هرچه وابستگی های یک کلاس کمتر باشد.. تغییر، گسترش یا استفاده مجدد از آن آسان تر می شود.
خلبانان هواپیمایی که به منطقه کنترل فرودگاه نزدیک یا از آن خارج می شوند ، مستقیماً با یکدیگر ارتباط برقرار نمی کنند. در عوض، آنها با یک کنترل کننده ترافیک هوایی صحبت می کنند ، که در برج مراقبت در جایی در نزدیکی خط هوایی نشسته است. بدون کنترل کننده ترافیک هوایی، خلبانان باید از وجود هر هواپیمایی در مجاورت فرودگاه آگاه باشند و در مورد اولویت های فرود با کمیته ای از ده ها خلبان دیگر بحث کنند. که انجام این کار، احتمالاً آمار سقوط هواپیما ها را به شدت افزایش می دهد.
برج مراقبت نیازی به کنترل کل پروسه پرواز ندارد; و دلیل وجودش اعمال محدودیت در حیطه ترمینال است.
۱) کامپوننت ها کلاس های مختلفی هستند که بیزنس لاجیک (business logic) دارند. هر کامپوننت رفرنسی به یک میانجی دارد که از طریق اینترفیس میانجی اعمال شده است. این کامپوننت هیچ اطلاعی از کلاس اصلی میانجی ندارد... پس می توانید با پیوند دادن آن به یک میانجی متفاوت، از آن در برنامه های دیگر استفاده کنید.
۲) اینترفیس میانجی روشهای ارتباط بین اجزا را که معمولاً فقط شامل یک متد اطلاع رسانی واحد است، اعلام می کند. و اجزاء میتوانند داده ای را به عنوان آرگومان این متد (حتی خود اشیاء را) ارسال کنند ، اما به گونه ای که هیچگونه وابستگی بین یک کلاس دریافت کننده و کلاس فرستنده رخ ندهد.
۳) میانجی های ثابت (Concrete) روابط بین کامپوننت ها را کپسوله سازی میکنند. میانجی های ثابت اغلب رفرنسی را به تمام کامپوننت هایی که مدیریت میکنند، ذخیره میکنند. (در برخی موارد حتی رفرنس های چرخه حیات کامپوننت ها را هم ذخیره میکنند)
۴) کامپوننت ها نباید از وجود یکدیگر مطلع باشند. اگر اتفاق مهمی در داخل و یا برای یک کامپوننت رخ دهد ، این کامپوننت تنها باید به میانجی اطلاع رسانی کند. هنگامی که میانجی اعلان را دریافت می کند، می تواند به راحتی فرستنده را شناسایی کند ، که ممکن است برای تصمیم گیری در انتخاب عملکرد کافی باشد....
پس یک کامپوننت در نادانی مطلق به سر میبرد.. فرستنده نمی داند چه کسی به درخواست خورسیدگی میکند و گیرنده نمیداند که چه کسی در وهله اول درخواست را ارسال کرده است.
۱) مواقعی که تغییر برخی از کلاس ها دشوار است. (زیرا آنها به شدت با کلاس های دیگر گره خوردهاند)
۲) زمانی که نتوانید از یک کامپوننت در برنامه دیگری استفاده کنید زیرا بیش از حد به کامپوننت های دیگر وابسته است.
۳) زمانی که تنها برای استفاده مجدد از برخی رفتارها، ساب-کلاس های زیادی میسازید.
۱) گروهی از کلاسهای در هم تنیده را انتخاب کنید که از مستقل بودن سود بیشتری می برند. (به عنوان مثال، برای نگهداری آسانتر یا استفاده ساده تر از این کلاسها)
۲) اینترفیس میانجی را بسازید و پروتکل ارتباطی مورد نظر بین میانجی ها و کامپوننت های مختلف را مشخص کنید. در بیشتر موارد، یک متد واحد برای دریافت اعلانات از کامپوننت ها کافی است.
هنگام استفاده مجدد از کامپوننت ها، وجود اینترفیس حیاتی است. مادامی که کامپوننت با میانجی اش، از طریق اینترفیس در ارتباط باشد; میتوانیم کامپوننت را با میانجی های دیگر هم پیوند دهیم.
۳) کلاس ثابت میانجی را پیاده سازی کنید. این کلاس از ذخیره رفرنسها برای همه کامپوننت هایی که مدیریت می کند سود می برد.
۴) حتی می توانید فراتر بروید و میانجی را مسئول ایجاد و نابودی اجسام اجرایی کنید. پس از این ، میانجی ممکن است شبیه یک Factory و یا Facade به نظر آید.
۵) کامپوننت ها باید رفرنس شیء میانجی را ذخیره کنند. اتصال معمولاً در متد سازنده کامپوننت برقرار می شود ، جایی که یک شی میانجی به عنوان آرگومان منتقل می شود.
۶) کد کامپوننت ها را طوری تغییر دهید که به جای متد های موجود در سایر کامپوننت ها، متد اطلاع رسانی میانجی را فراخوانی کنند.
الگوی Mediator یک سری شباهت ها با الگوهای Chain of Responsibility, Command و Observer دارد.. در واقع این الگوها روشهای مختلف اتصال، بین فرستنده ها و گیرنده های درخواست را آدرس دهی میکنند.
الگوی Chain of Responsibility : یک درخواست را به طور متوالی از طریق یک زنجیره پویا از گیرنده های بالقوه منتقل می کند، تا زمانی که یکی از زنجیره ها به درخواست رسیدگی کند.
الگوی Command : برای برقراری ارتباطات یک طرفه بین فرستنده ها و گیرنده ها مورد استفاده قرار میگیرد.
الگوی Mediator : ارتباط مستقیم بین فرستنده ها و گیرنده ها را از بین می برد، و آنها را مجبور می کند تا به طور غیر مستقیم از طریق یک شئ واسطه ارتباط برقرار کنند.
و الگوی Observer : به گیرنده ها اجازه می دهد تا به صورت داینامیک در دریافت درخواست ها مشترک شوند و یا اشتراکشان را لغو کنند.
الگوهای Facade و Mediator وظایف یکسانی دارند. آنها سعی در سازماندهی همکاری بین کلاس های در هم تنیده را دارند.
الگوهای Mediator و Observer تفاوتی نسبتا جزئی دارند و بعضی مواقع میتوانید این ۲ گزینه را به جای هم تعبیه کنید.
هدف اصلی Mediator از بین بردن وابستگی های متقابل در میان مجموعه ای از کامپوننت های سیستم است. در عوض این کامپوننت ها میتوانند به یک شئ میانی وابسته باشند. اما هدف اصلی Observer برقراری ارتباطات داینامیک یک طرفه میان اشیا ست. که ممکن است بعضی از این اشیا زیر مجموعه سایر اشیا باشند.
همچنین سبکی معروف از پیاده سازی الگوی Mediator وجود دارد که متکی به الگوی Observer است. در این حالت شئ میانی نقش یک ناشر را بازی میکند و کامپوننت ها نقش مشترکانی را دارند که رویدادهای شئ میانی را دنبال میکنند. پیاده سازی الگوی Mediator به این شکل بسیار به الگوی Observer شباهت دارد.
فراموش نکنید که الگوی Mediator به شکل های دیگری هم قابل پیاده سازی است. مثلا میتوانید تمام کامپوننت ها رو به شکل دائمی به یک شئ میانی وصل کنید که در این حال شباهت ها با الگوی Observer کمتر و کمتر میشود.
حالا برنامه ای را تصور کنید که تمام کامپوننت های آن، تبدیل به ناشر شده باشند; و امکان ایجاد ارتباط داینامیک بین یکدیگر را فراهم کرده اند. در این حالت هیچ شئ میانی که مرکزی باشد، وجود ندارد; و تنها مجموعه ای توضیع شده از ناظر ها (Observers) وجود دارند.
در زیر لیستی از زبان هایی قرار داره که میتونید پیاده سازی الگوی طراحی Observer رو در اونها بررسی کنید: (امیدوارم زبان برنامه نویسی شما در لیست زیر باشه، اگر هم نیست میتونید مثالتون رو آماده کنید و یه پول ریکوئست بزنید..)
ریپازیتوری:
https://github.com/AliBayat/MediatorPatternExamples
منبع:
Dive Into Design Patterns