الگوی Observer یک الگوی طراحی رفتاری (Behavioral) است که به ما امکان می دهد مکانیزمی داشته باشیم، برای اطلاع رسانی تغییرات یک آبجکت به چندین آبجکت دیگر.
به عبارت دیگر هر گاه آبجکتی توسط آبجکت های دیگر مورد مشاهده (Observation) قرار گیرد، در صورت ایجاد تغییر در آن آبجکت، آبجکت های مشاهده کننده میتوانند از آن تغییرات مطلع شوند.
تصور کنید دو نوع آبجکت داریم: مشتری و فروشگاه. و مشتری علاقه زیادی به مارک خاصی از محصول داره (مثلاً مدل جدیدی از آیفون) که به زودی قراره در فروشگاه موجود باشه و فروخته بشه.
مشتری می تواند هر روز به فروشگاه مراجعه کند و در دسترس بودن محصول را بررسی کند. اما مادامی که محصول هنوز به فروشگاه نرسیده است، بیشتر این مراجعه ها بی فایده هستند.
از طرفی، فروشگاه می تواند هر بار كه محصول جدیدی در دسترس است ، تعداد زیادی ایمیل (كه احتمالاً اسپم محسوب می شوند) را برای همه مشتریان ارسال كند. این باعث می شود برخی از مشتریان از مراجعه بی پایان به فروشگاه نجات پیدا کنند. اما در عین حال ، این باعث ناراحتی سایر مشتریانی میشود که علاقه ای به اطلاع یافتن از محصولات جدید ندارند.
به نظر می رسد اینجا یک تضاد داشته باشیم.. یا مشتری وقت خود را برای بررسی در دسترس بودن محصول هدر می دهد و یا فروشگاه منابع خود را برای اطلاع رسانی به مشتریانِ اشتباه هدر می دهد..
آبجکتی که دارای اِستیتی (وضعیت) است که به آن علاقه مندیم غالباً سوژه نامیده می شود، اما از آنجا که این آبجکت قصد دارد سایر آبجکت ها را نیز در مورد تغییرات وضعیت خود آگاه سازد، آنرا ناشر و یا (Publisher) می نامیم. همه آبجکت های دیگری که می خواهند تغییرات مورد نظر در وضعیت ناشر را پیگیری کنند، مشترک و یا (Subscriber) نامیده می شوند.
الگوی Observer پیشنهاد می کند که شما یک مکانیزم اشتراک را به کلاس ناشر اضافه کنید تا اشیا به صورت جداگانه بتوانند، در جریان رویداد های ناشی از آن ناشر مشترک شوند و یا اشتراک خود را لغو کنند.
مسئله آنطور که به نظر می رسد پیچیده نیست; در حقیقت این مکانیزم شامل موارد زیر است:
اکنون، هر زمان که رویدادی مهم برای ناشر رخ دهد، مشترکان خود را بررسی میکند و متد اطلاع رسانی خاصی را برای آن آبجکت ها فراخوانی می کند.
برنامه های واقعی ممکن است ده ها کلاس از مشترکان مختلف داشته باشند که علاقه مند به ردیابی رویدادهای کلاس ناشر هستند. پس بسیار مهم است که همه مشترکان از یک اینترفیس استفاده کنند و ناشر فقط از طریق آن اینترفیس با آنها در ارتباط باشد.
این اینترفیس باید متدی را برای اطلاع رسانی به همراه مجموعه ای از پارامترها مشخص کند، تا ناشر بتواند در کنار اعلان تغییرات، یک سری داده را نیز پاس بدهد.
اگر برنامه شما چندین نوع ناشر مختلف دارد و می خواهید مشترکان را با همه آنها سازگار کنید ، می توانید از این هم فراتر رفته و همه ناشران را مجبور کنید تا از یک اینترفیس تبعیت کنند. این اینترفیس تنها باید یک سری متد های مربوط به مشترکان را اعمال کند. این اینترفیس به مشترکان اجازه می دهد تا بدون وابستگی به کلاس های ثابت (concrete) از وضعیت ناشران مطلع شوند.
اگر شما مشترک روزنامه یا مجله ای باشید، برای اطلاع از اومدن نسخه جدیدش، نیازی به رفتن به فروشگاه یا دفتر آن مجله ندارید. در عوض ناشر که لیستی از مشترکان را دارد و میداند هر یک به چه مجله یا روزنامه ای علاقه مند هستند، بلافاصله پس از انتشار یا حتی قبل از آن نسخه جدید را مستقیماً به صندوق پستی شما ارسال می کند.
همچنین شما به عنوان یک مشترک هر زمان که خواستید میتوانید اشتراک خود را لغو کنید، تا مجله یا روزنامه ای برای شما ارسال نشود.
۱) ناشر رویدادهای مورد علاقه سایر آبجکت ها را صادر می کند. این رویدادها زمانی اتفاق می افتند که ناشر وضعیت خود را تغییر دهد یا برخی رفتارها را انجام دهد. همچنین ناشر دارای زیرساختی است که اجازه میدهد: مشترکان جدید به لیست اضافه شده و یا مشترکین فعلی از لیست حذف شوند.
۲) هنگامی که یک رویداد جدید اتفاق می افتد ، ناشر لیست مشترکان را پیمایش می کند و متد اطلاع رسانی ای که در اینترفیس مشترکان مشخص شده است، را روی آبجکت مشترکان فراخوانی می کند.
۳) اینترفیس مشترکان یک رابط اطلاع رسانی را اعمال میکند; که اکثر مواقع یک متد update ساده است که میتواند پارامترهایی را هم بپذیرد و به ناشر اجازه دهد تا جزئیات رویداد را هم انتقال دهد.
۴) مشترکان برخی اقدامات را در پاسخ به اعلان های صادر شده توسط ناشر انجام می دهند. همه این مشترکان باید از یک اینترفیس تبعیت کنند; تا از در هم تنیده شدن ناشر و کلاسهای concrete جلوگیری شود.
۵) معمولا مشترکان برای هندل کردن پروسه آپدیت به شکلی صحیح ، به یک سری داده نیاز دارند. به همین دلیل ناشران اغلب برخی از داده ها را به عنوان آرگومان های متد اطلاع رسانی (notify) منتقل می کنند. در صورت نیاز ناشر میتواند خودش را نیز به عنوان آرگومان پاس داده و به مشترک اجازه دهد تا داده های مورد نیازش را مستقیما واکشی کند.
۶) در نهایت آبجکت های ناشر و مشترکان را جداگانه میسازیم و سپس لیست مشترکان را برای دریافت آپدیت های آن ناشر ثبت میکنیم.
۱) هنگامی که تغییر در وضعیت یک شئ، نیاز به تغییرات در سایر اشیا داشته باشد و یا تغییرات به صورت داینامیک صورت بگیرند، میتوانید از این الگو استفاده کنید.
۲) الگوی Observer به هر آبجکتی که از اینترفیس Subscriber تبعیت کند، اجازه میدهد تا رویدادهای آبجکت Publisher را دنبال کند.
ابتدا بیزنس لاجیک (business logic) را بررسی کنید و سعی کنید آن را به دو قسمت تقسیم کنید: عملکرد های اصلی، جدا از سایر کد ها، نقش ناشر (Publisher) را خواهند داشت و باقی به مجموعه ای از کلاس های مشترک (Subscriber) تبدیل خواهند شد.
حالا اینترفیس مشترکان رو میسازیم که باید حداقل ۱ متد update داشته باشد.
سپس اینترفیس ناشر رو میسازیم به همراه ۲ متد برای افزودن و حذف مشترکان.
حالا باید متدهای مربوط به اشتراک رو تعریف کنیم. معمولا این قسمت از کد ها برای تمام ناشر ها یکسان هستند; پس گذاشتن این کدها داخل یک کلاس ابسترکت جای مناسبی به نظر میرسه. به این تریتیب کلاس های ناشر ثابت (concrete) میتوانند از این کلاس ارث برای کنند.
حال کلاس های ناشر از نوع ثابت (concrete) ایجاد کنید. و هر بار که اتفاق مهمی در داخل ناشر رخ می دهد ، این رویداد را به همه مشترکان آن اطلاع دهید.
حال متد update را در کلاسهای مشترکان ثابت تعبیه کنید. به این ترتیب با دریافت اعلان، مشترک می تواند هر داده ای را مستقیماً از اعلان دریافت کند.
الگوی Observer یک سری شباهت ها با الگوهای Chain of Responsibility, Command و Mediator دارد.. در واقع این الگوها روشهای مختلف اتصال، بین فرستنده ها و گیرنده های درخواست را آدرس دهی میکنند.
الگوی Chain of Responsibility : یک درخواست را به طور متوالی از طریق یک زنجیره پویا از گیرنده های بالقوه منتقل می کند، تا زمانی که یکی از زنجیره ها به درخواست رسیدگی کند.
الگوی Command : برای برقراری ارتباطات یک طرفه بین فرستنده ها و گیرنده ها مورد استفاده قرار میگیرد.
الگوی Mediator : ارتباط مستقیم بین فرستنده ها و گیرنده ها را از بین می برد، و آنها را مجبور می کند تا به طور غیر مستقیم از طریق یک شئ واسطه ارتباط برقرار کنند.
و الگوی Observer : به گیرنده ها اجازه می دهد تا به صورت داینامیک در دریافت درخواست ها مشترک شوند و یا اشتراکشان را لغو کنند.
الگوهای Mediator و Observer تفاوتی نسبتا جزئی دارند و بعضی مواقع میتوانید این ۲ گزینه را به جای هم تعبیه کنید.
هدف اصلی Mediator از بین بردن وابستگی های متقابل در میان مجموعه ای از کامپوننت های سیستم است. در عوض این کامپوننت ها میتوانند به یک شئ میانی وابسته باشند. اما هدف اصلی Observer برقراری ارتباطات داینامیک یک طرفه میان اشیا ست. که ممکن است بعضی از این اشیا زیر مجموعه سایر اشیا باشند.
همچنین سبکی معروف از پیاده سازی الگوی Mediator وجود دارد که متکی به الگوی Observer است. در این حالت شئ میانی نقش یک ناشر را بازی میکند و کامپوننت ها نقش مشترکانی را دارند که رویدادهای شئ میانی را دنبال میکنند. پیاده سازی الگوی Mediator به این شکل بسیار به الگوی Observer شباهت دارد.
فراموش نکنید که الگوی Mediator به شکل های دیگری هم قابل پیاده سازی است. مثلا میتوانید تمام کامپوننت ها رو به شکل دائمی به یک شئ میانی وصل کنید که در این حال شباهت ها با الگوی Observer کمتر و کمتر میشود.
حالا برنامه ای را تصور کنید که تمام کامپوننت های آن، تبدیل به ناشر شده باشند; و امکان ایجاد ارتباط داینامیک بین یکدیگر را فراهم کرده اند. در این حالت هیچ شئ میانی که مرکزی باشد، وجود ندارد; و تنها مجموعه ای توضیع شده از ناظر ها (Observers) وجود دارند.
یکی از جذابیت های الگوهای طراحی همه گیر بودن اونهاست. سوای اینکه از چه زبانی استفاده میکنید، مادامی که زبان برنامه نویسی شما از شئ گرایی پشتیبانی کنه، ما به راحتی منظور هم رو متوجه میشیم.
در زیر لیستی از زبان هایی قرار داره که میتونید پیاده سازی الگوی طراحی Observer رو در اونها بررسی کنید: (امیدوارم زبان برنامه نویسی شما در لیست زیر باشه، اگر هم نیست میتونید مثالتون رو آماده کنید و یه پول ریکوئست بزنید..)
ریپازیتوری:
https://github.com/AliBayat/ObserverPatternExamples
منبع:
Dive Into Design Patterns