طراحی خوب نرمافزار یکی از مهمترین اهداف هر مهندس نرمافزاری است. یک طراحی خوب به تولید نرمافزاری منجر میشود که قابلیت استفادهی دوباره (Reuse) و انعطافپذیری خوبی داشته باشد تا احتمال طراحی و پیادهسازی دوباره را کمینه کند و نیز بتواند به نیازمندیهای ناشناختهی آینده پاسخ دهد. در گذشته ایجاد یک طراحی خوب فقط با تجربه و انجام پروژههای متعدد ممکن بوده، اما امروزه به کمک مفهوم الگوها برای افراد کمتجربهتر نیز ممکن شده است. هر الگو یک مسئلهی پرتکرار در مهندسی نرمافزار را به همراه شرایط آن توصیف میکند و برای آن عصارهی یک راه حل آزموده شده و کاری ارائه میدهد که بتوان آن را چندین و چند بار در محیطهای متفاوت استفاده کرد.
الگوهای طراحی اولین بار بیش از ۲۰ سال پیش در کتابDesign Patterns: Elements of Reusable Object-Oriented Software برای پارادایم شئگرایی - پرکاربردترین روش طراحی نرمافزار - معرفی شدند. با این وجود، این الگوها هنوز که هنوز است در صنعت استفاده میشوند و کمتر پروژهای پیدا میشود که آنها را شامل نشود. همچنین، این ایده که در حوزههای مختلف با الگوها کار کنیم یکی از مهمترین نتایج این کتاب بود و بسیار مورد توجه قرار گرفت. در این نوشته به بررسی ساختار بعضی از این الگوها و نحوهی انتخاب آنها برای استفاده در پروژههای مختلف پرداخته میشود.
چرا الگوهای طراحی؟
الگوهای طراحی بسیاری از مسائلی که به روزانه در زندگی کاری یک مهندسی نرمافزار دیده میشوند را حل میکند و در نتیجه، باعث افزایش سرعت و دقت فرآیند توسعه میشود. چند مورد از مسائلی که این الگوها و به طور خاص الگوهای Gang of Four حل میکنند در ادامه شرح داده خواهند شد.
تشخیص اشیاء مناسب: برنامههای شئگرا از اشیاء ساخته شدهاند، که بستههاییند از دادهها و اعمالی که روی آنها اثر میکنند. تنها روشی که از دنیای بیرون یک شئ میتوان با آن ارتباط برقرار کرد، استفاده از اعمال آن است. این موضوع تحت عنوان Encapsulation شناخته میشود و یکی از اصول بسیار مفید برنامهنویسی است. با این همه، تشخیص این که چه شئهایی باید در برنامه در نظر گرفته شوند تا یک سامانه ایجاد شود کار سادهای نیست و به عوامل زیادی وابسته است. این موضوع که دستهای نسبتاً بزرگ از اشیاء در دنیای واقعی مربوط به مسئله تعریف نمیشوند شرایط را سختتر نیز میکند. الگوهای طراحی کمک میکنند که سطوح انتزاع مناسب در پیادهسازی در نظر گرفته شوند و با استفاده از اشیاء مناسب پیادهسازی شوند.
تنظیم ریزدانگی اشیاء: اشیاء میتوانند هر چیزی را نمایش دهند، از یک سیستم کامل گرفته تا اجزاء سختافزاریای که نرمافزار روی آن اجرا میشود. الگوهای طراحی از این نظر که سطوح ریزدانگی مختلفی را پشتیبانی میکنند بسیار مفیدند.
تعریف رابطهای اشیاء: رابطها یا Interfaceها در برنامهنویسی شئگرا یکی از عناصر کلیدیاند، چرا که یک شئ از بیرون فقط با رابطش شناخته میشود. یکی از اثرات وجود رابطها، این است که از بیرون اشیاء اطلاعاتی در رابطه با نحوهی پیادهسازی توابع آن در دسترس نیست و فقط در زمان اجرا است که خود پیادهسازی و منطق آن مشخص میشود. این پدیده تحت عنوان Dynamic Binding شناخته میشود، که استفاده از آن میتواند به ایجاد مفهوم Polymorphism منجر شود. الگوهای طراحی در تعریف رابطها کمک شایانی به طراح میکنند، چرا که اجزاء لازم آنها و بعضاً مواردی که نباید در رابط قرار گیرند را مشخص میکنند. علاوه بر این، شروطی متقابل بین رابطها میتوانند تعریف کنند که در طراحی سایر رابطها میتوانند مفید واقع شوند.
راهنمایی در پیادهسازی یک شئ: پیادهسازی اشیاء از طریق تعریف کلاسهای آنها انجام میشود. الگوهای طراحی علاوه بر این که ساختار کلی و سطح بالای پیادهسازی را مشخص میکنند، نمونه کدهایی برای پیادهسازی آنها نیز ارائه میدهند که میتوانند خواننده را در انجام پروژهی خویش یار کند. همچنین، با توجه به این که الگوها برای ایجاد رابطهای خوب به دست آمدهاند، کاربر را طوری راهنمایی میکنند که نه تنها در میزان پیادهسازی خود صرفهجویی کنند، بلکه رابطهای مشترک و خوشتعریف زیادی داشته باشند تا در شرایط بیشتری بتوانند از کد خود دوباره استفاده کنند.
استفادهی دوباره از کد: یکی از دلایل استفاده از شئگرایی کم کردن زائد و استفادهی دوباره از کد است. به همین دلیل است که مفهوم وراثت در آن تعریف شده است. اما این مفهوم برای استفادهی دوباره لزوماً ایدهآل نیست، چرا که تعمیم یک کلاس پدر به یک کلاس فرزند با اضافه کردن یا تغییر یک عملیات آن، ممکن است نیازمند تغییر سایر عملیاتهای کلاس نیز باشد. جدای از این، پیادهسازی کلاسهای فرزند را نمیتوان در زمان اجرا عوض کرد. به همین است که الگوهای طراحی و به طور Gang of Four، پیشنهاد میکنند که از ترکیب اشیاء یا Object Composition استفاده شود. هدف این است که برای استفادهی دوباره از کد موجود لازم نباشد که المان جدید تعریف شود و فقط با ترکیب المانهای موجود، کاربردهای جدید پشتیبانی شوند. در نتیجه، در زمان اجرا نیز میتوان ساختار و منطق اجزاء را تغییر داد. اما برای این لازم است که روابط اشیاء با اطلاع از روشهایی که ممکن است از آنها استفاده شود و یا اشیاء دیگر تعریف شده باشند که کمی پیچیده است. این موضوع در الگوهای طراحی در نظر گرفته شده است و نیازی به نگرانی بیش از حد در مورد آن نیست. لازم به ذکر است که با استفاده از مفهوم Delegation میتوان همان منطق وراثت را در این ایده ایجاد کرد. در نتیجه، از قدرت پیادهسازی کم نمیشود.
ساختارهای مرتبط زمان اجرا و زمان کامپایل: بسیاری از شروط و ساختارها در زمان اجرا فقط معنی دارند و با دیدن خود متن پیادهسازی نمیتوان از آنها اطلاع کسب کرد. الگوهای طراحی این روابط را ثبت کردهاند و با اطلاع دادن به طراح، پیادهسازی و فهم سیستم را تسهیل میکنند.
طراحی برای تغییر: از آنجایی که تمام پروژههای واقعی در طول عمرشان دچار تغییر میشوند، لازم است که اجراء متغیر آن پیشبینی شوند و برای آنها طراحی انجام شود. الگوهای طراحی در این زمینه نیز اقدام کردهاند و بسیاری از اجزاء متغیر را طوری طراحی کردهاند که بعداً به راحتی تعمیم داده شوند. این موضوع باعث میشود که فرآیند اعمال تغییر بسیار سادهتر از قبل شود و نیاز به تغییر به کد موجود کمینه شود.
علاوه بر همهی این موارد، یکی از کاربردهای مهم الگوهای طراحی در مستندات پروژههاست، چرا که مفاهیم پرکاربرد و پیچیدهای را نامگذاری میکند و ارتباط بین افراد یک تیم را تسهیل میکند. این دایرهی واژگان گسترش یافته ساختار ذهنی یک فرد را نیز تغییر میدهد، طوری که بتوان به مسائل بهتر فکر کند.
ساختار یک الگو
روشهای توصیف زیادی برای الگوها داریم، اما همهی آنها در چهار چیز مشترکند: ۱) نامی مناسب دارند که ذات الگو را توصیف میکند، طوری که به خوبی بتوان از آن در مستندات و بحثهای روزمره استفاده کرد، ۲) مسئلهی مد نظر را توصیف کند، به همراه شرایطی که در آن این مسئله تعریف شده است، ۳) راه حلی آزموده شده ارائه دهد و ۴) عواقب و اثرات استفاده از آن الگو را شرح دهد. در این نوشته از ساختار زیر برای شرح الگوهای طراحی Gang of Four استفاده میشود:
نام الگو: بسیار مهم است چرا که قرار است به واژهنامهی طراحی افزوده شود.
قصد: کاری که الگوی مورد نظر برای آن طراحی شده است و مشکلی که حل میکندرا شرح میدهد.
مسئله: یک مثال از یک مسئلهای که الگو حل میکند را مطرح میکند.
راه حل: ساختار کلی راه حل مسئلهی مذکور را ارائه میدهد.
کاربردها: شرایطی که میتوان از این الگو استفاده نمود را شرح میدهد.
چگونگی پیادهسازی: روند کلی پیادهسازی الگو را ارائه میدهد.
خوبیها و بدیها: عواقب و اثرات استفاده از الگو را شرح میدهد.
ارتباط با سایر الگوها: نسبت این الگو را با الگوهای دیگر Gang of Four ارائه میدهد.
روش انتخاب الگو
صرف این که الگوهای طراحی Gang of Four در اختیار ی مهندس باشد کافی نیست و لازم است که او توان انتخاب الگوی مناسب از بین آنها برای مسائل خود را داشته باشد، چرا که انتخاب الگوی غلط میتواند باعث ساختار نامفهوم طراحی، تغییر و تعمیم سخت و کاهش قابلیت استفادهی دوباره شود. در این راستا، پیشنهاد میشود که در انتخاب الگو به موارد زیر توجه شود:
چگونگی حل مسائل توسط الگوها در نظر گرفته شود. این کار باعث تشخیص ارتباط راه حل با مشکل موجود میشود و نیز میتواند باعث شود که الگوهای مرتبط دیگری در جستجو به دنبال راه حل در نظر گرفته شود.
نمونه مسائلی که مد نظر هر الگو است اول کار بررسی شود. با دانستن مجموعهی مسائلی که برایشان راه حل در دست است، میتوان الگوی بهتری پیدا کرد.
رابطهی بین الگوهای مختلف بررسی شود. شکل زیر ارتباط الگوهای Gang of Four به هم را به تصویر کشیده است. استفاده از آن و نیز استفاده از بخش «ارتباط با سایر الگوها»ی فهرست الگوها میتواند به پیدا کردن راه حلهای مناسبتر منجر شود.
دلیل رخ دادن طراحی دوباره بررسی شود. با آسیبشناسی شرایط، مسئلهی مد نظر بهتر درک میشود و راه حل مناسب آن واضحتر میشود.
مواردی که لازم است در طراحی متغیر باشد در نظر گرفته شود. درواقع برعکس مورد قبل است و با پیدا کردن عوامل احتمالی رخ دادن تغییر در طراحی، ذات الگوی مورد نیاز بهتر درک میشود.
نمونههایی از الگوهای طراحی Gang of Four
الگوهای طراحی Gang of Four بر حسب این که چه نوع مسئلهای را در نظر میگیرند و حل میکنند به سه دستهی عمدهی سازنده (Creational)، ساختاری (Structural) و رفتاری (Behavioral) تقسیم میشوند. در ادامه، هر کدام از این دستهها تعریف میشوند و مثالهای از مهمترین الگوهای هر کدام ارائه خواهند شد.
الگوهای سازنده یا Creational
این الگوها مسائل مربوط به ایجاد و ساختن اشیاء را در نظر میگیرند و باعث انعطافپذیری و استفادهی دوباره از کد میشوند. در ادامه چند نمونه از مهمترین الگوهای سازنده ارائه خواهند شد.
الگوی Factory Method
قصد: الگوی Factory Method یک الگوی طراحی ساختاری است که به کاربر این امکان را میدهد تا ایجاد یک شئ را به یک زیرکلاس از آن شئ محول کند. این الگو برای ایجاد انتزاعی از فرآیند ساخت استفاده میشود و به کاربر اجازه میدهد تا مراحل دقیق ساخت یک شئ را در زمان اجرا تعیین کند.
مسئله: موقعیتی که الگوی Factory Method مناسب است، زمانی است که یک کلاس نیاز به ایجاد شئهایی از یک خانواده مشترک دارد، اما در زمان اجرا میتواند تعیین کند کدام زیرکلاس از آن شئ باید ایجاد شود. همچنین، این الگو مناسب است وقتی که میخواهیم روش ساخت یک شئ را در یک کلاس اصلی تعیین کنیم، اما بخشهای خاصی از فرآیند ساخت را به زیرکلاسها واگذار کنیم.
راه حل: برای استفاده از الگوی Factory Method، میتوانیم مراحل زیر را طی کنیم:
تعیین یک رابطه (Interface) یا کلاس پایه (Base Class) که شئهای مختلف را توصیف کند.
تعریف یک رابطه (Interface) یا کلاس پایه (Base Class) برای کارخانه (Factory) که متد ساخت (Create) را دارد و به کلاسهای زیرکلاس اجازه میدهد تا شئهای مورد نیاز خود را ایجاد کنند.
پیادهسازی کلاسهای زیرکلاس (Subclasses) از کارخانه (Factory) برای ساخت شئهای مورد نیاز. هر زیرکلاس میتواند روش ساخت (Create) را به شکل خود تعریف کند و به این ترتیب میتواند فرآیند ساخت را سفارشی کند.
استفاده از کارخانه (Factory) برای ساخت شئهای مورد نیاز در کلاسهای دیگر.
با استفاده از الگوی Factory Method، کاربر قادر است به جای ایجاد مستقیم شئها، از کارخانه استفاده کند و میزان اتصال به زیرکلاسها را به حداقل برساند. همچنین، میتواند در زمان اجرا تعیین کند کدام زیرکلاس باید ایجاد شود، بدون تغییر در کلاسهای دیگر.
کاربردها:
انتزاع در ساخت: استفاده از الگوی Factory Method به کاربر امکان میدهد تا از جزئیات فنی ساخت یک شئ خبر نداشته باشد و فقط با استفاده از رابطه کارخانه و متد ساخت، شئ مورد نیاز را ایجاد کند. این انتزاع در ساخت باعث میشود تغییر در پیادهسازی زیرکلاسها تأثیری در سایر بخشهای کد نداشته باشد.
پیچیدگی کاهش مییابد: با استفاده از الگوی Factory Method، پیچیدگی ساخت یک شئ کاهش مییابد. کاربران برای ایجاد شئ، فقط با رابطه کارخانه و متد ساخت در ارتباط هستند و جزئیات پیچیده و مربوط به ساخت در زیرکلاسها پنهان میشود.
امکان افزودن زیرکلاسهای جدید: با استفاده از الگوی Factory Method، به راحتی میتوان زیرکلاسهای جدیدی را به سیستم اضافه کرد. کاربران میتوانند زیرکلاسهای جدیدی ایجاد کنند و متد ساخت را برای آنها پیادهسازی کنند، بدون اینکه نیاز به تغییر در کلاسهای دیگر باشد.
انتخاب دقیق زیرکلاس در زمان اجرا: یکی از مزیتهای الگوی Factory Method این است که به کاربر اجازه میدهد تا در زمان اجرا تعیین کند کدام زیرکلاس باید استفاده شود. این امر باعث میشود که مدیریت پویا و انعطافپذیری در انتخاب زیرکلاسها امکانپذیر شود.
تست واحد سادهتر: استفاده از الگوی Factory Method باعث میشود تست واحد برای کلاسها و زیرکلاسها سادهتر شود. با استفاده از Mocking و استفاده از کلاسهای کارخانه جعلی (Fake Factory)، میتوان به راحتی تستهای واحد را انجام داد و کنترل بیشتری بر روی ساخت شئها داشت.
چگونگی پیادهسازی:
تعریف یک رابطه (Interface) یا کلاس پایه (Base Class): در این رابطه یا کلاس پایه، متدی برای ساخت شئها تعریف میشود. این متد عملیات ساخت را تعیین کرده و شئ مورد نظر را برمیگرداند. این رابطه یا کلاس پایه میتواند یک abstract class یا یک interface باشد.
پیادهسازی کلاسهای زیرکلاس (Subclass): برای هر نوع شئ مورد نیاز، یک کلاس زیرکلاس برای رابطه یا کلاس پایه تعریف میشود. این کلاسها باید متد ساخت را پیادهسازی کنند و شئ مورد نظر را ایجاد کنند. هر کلاس زیرکلاس میتواند عملکردهای مختلف و منحصر به فرد خود را داشته باشد.
استفاده از الگوی Factory Method: در قسمتهایی از کد که نیاز به ایجاد شئ دارند، از رابطه یا کلاس پایه استفاده میشود. با فراخوانی متد ساخت این رابطه یا کلاس پایه، یک شئ مورد نظر ایجاد میشود. اما نحتمل است در این قسمت کلاس زیرکلاسهای مختلف استفاده شود و براساس شرایط مورد نیاز، یکی از آنها انتخاب شود.
خوبیها:
بهبود قابلیت اطلاعرسانی و نگهداری کد: با استفاده از الگوی Factory Method، قابلیت اطلاعرسانی به کاربران در مورد ساختار و روند ساخت شئها افزایش مییابد. به عنوان مثال، با استفاده از نام یا توصیفی که در رابطه یا کلاس پایه تعریف شده است، کاربران میتوانند به راحتی شئ مورد نیاز خود را ایجاد کنند. همچنین، با جدا کردن جزئیات ساخت در کلاسهای زیرکلاس، نگهداری و توسعه کد نیز آسانتر میشود.
ارتقای قابلیت توسعه و گسترشپذیری: الگوی Factory Method به کاربران اجازه میدهد که با اضافه کردن کلاسهای زیرکلاس جدید، شئهای جدید را به سیستم اضافه کنند بدون اینکه نیاز به تغییر در کد موجود داشته باشد. این امکان به توسعهدهندگان کمک میکند تا به راحتی و بدون تغییر در کد موجود، تغییرات و بهبودهای جدید را اعمال کنند و سیستم را بهبود دهند.
بدیها:
اگر تعداد زیادی کلاس زیرکلاس وجود داشته باشد، ممکن است پیچیدگی کد و اداره کردن تعداد زیادی از کلاسها دشوار شود. زمانی که تعداد زیادی کلاس زیرکلاس وجود دارد، ممکن است پیدا کردن مناسبترین کلاس زیرکلاس برای ساخت شئ مورد نیاز، زمانبر و پیچیده شود.
اضافه کردن یا حذف کلاسهای زیرکلاس نیز ممکن است نیاز به تغییر در کد موجود داشته باشد، که این موضوع میتواند زمانبر و خطرناک باشد و احتمال وقوع خطا را افزایش دهد.
ارتباط با سایر الگوها:
الگوی Singleton: در برخی مواقع، الگوی Factory Method میتواند با الگوی Singleton ترکیب شود. در این صورت، میتوانیم یک فکتوری متد را به عنوان Singleton پیادهسازی کنیم تا فقط یک نمونه از آن فکتوری وجود داشته باشد و از طریق آن متدها و شیهای مورد نیاز را ایجاد کنیم.
الگوی Abstract Factory: الگوی Factory Method میتواند به عنوان بخشی از الگوی Abstract Factory استفاده شود. در این حالت، یک Abstract Factory با دستکاری و ارائه Factory Methodها برای ساخت شیهای خاص، ایجاد میشود.
الگوی Template Method: در الگوی Factory Method، ممکن است از الگوی Template Method برای پیادهسازی متد ساخت شئ استفاده شود. با استفاده از الگوی Template Method میتوانیم ساختار کلی الگوی Factory Method را تعریف کنیم و مراحلی که در فرایند ساخت شئ تغییر میکند را به کلاسهای زیرکلاس محول کنیم.
الگوی Strategy: در الگوی Factory Method، ممکن است از الگوی Strategy برای انتخاب و استفاده از یک روش مخصوص ساخت شئ استفاده شود. با استفاده از الگوی Strategy میتوانیم متد ساخت شئ را به یک کلاس استراتژیک تبدیل کنیم و به عنوان ورودی در فکتوری متد استفاده کنیم.
الگوی Decorator: الگوی Factory Method میتواند با الگوی Decorator ترکیب شود تا به شئهای ساخته شده امکان اضافه کردن عملکردها یا تغییر آنها را بدهد. با استفاده از الگوی Decorator، میتوانیم شیهای ساخته شده توسط Factory Method را با اضافه کردن عملکردهای جدید ویژگیهای اضافی بخشی از شئها را تغییر دهیم.
الگوی Abstract Factory
قصد: الگوی Abstract Factory برای ایجاد یک روشی انتزاعی برای ساختن مجموعهای از اشیاء مرتبط و متعلق به یک خانواده مشخص استفاده میشود. این الگو قصد دارد از جزئیات پیادهسازی فرآیند ساخت جدا شده و به کلاینت اجازه دهد اشیاء مرتبط را بدون انتزاع از نوع و کلاس آنها ایجاد کند.
مسئله: وقتی نیاز است تا مجموعهای از اشیاء متعلق به یک خانواده یا یک تجهیزات مشخص را بسازیم، اما نمیخواهیم برنامهی خود را به یک کلاس خاص مرتبط کنیم. این میتواند به دلیل تغییرات آینده در نحوه ساخت اشیاء یا نیاز به انتزاع در ایجاد اشیاء باشد.
راه حل: الگوی Abstract Factory به کلاینت اجازه میدهد تا با استفاده از یک رابطه عمومی، بدون در نظر گرفتن جزئیات پیادهسازی، مجموعهای از اشیاء متعلق به یک خانواده را ایجاد کند. این الگو از دو سطح انتزاعی استفاده میکند. سطح اول Abstract Factory است که یک رابطه عمومی برای ایجاد اشیاء مرتبط ارائه میدهد، و سطح دوم Concrete Factories است که پیادهسازیهای خاصی از Abstract Factory هستند و عملیات ایجاد اشیاء را بر عهده دارند.
کاربردها:
ساختار سازمانی: در یک ساختار سازمانی، میتوان از Abstract Factory استفاده کرد تا یک سیستم سلسله مراتبی از اشیاء مرتبط را بسازد. به عنوان مثال، در یک برنامه بانکی، میتوانیم از Abstract Factory برای ساخت اشیاء مرتبط با حسابهای بانکی (مانند حساب جاری، حساب سپرده و حساب قرض الحسنه) استفاده کنیم.
مدیریت پلاگینها: با استفاده از Abstract Factory، میتوانیم پلاگینها را به صورت پویا در برنامهها اضافه کنیم و مدیریت کنیم. به عنوان مثال، در یک سیستم مدیریت محتوا، میتوانیم از Abstract Factory برای ایجاد اشیاء مرتبط با انواع مختلف پلاگینها مانند ویرایشگرهای متنی، ابزارهای تصویرسازی و ماژولهای امنیتی استفاده کنیم.
ساخت سفارشی: با استفاده از Abstract Factory، میتوانیم به کاربران امکان سفارشیسازی و انتخاب اشیاء بر اساس نیازهای خاص خود را بدهیم.
چگونگی پیادهسازی:
تعریف اینترفیس Abstract Factory با متدهایی که اشیاء مرتبط را ایجاد میکند.
پیادهسازی کلاسهای Concrete Factory که Abstract Factory را پیادهسازی میکنند و اشیاء مرتبط را ایجاد میکنند.
تعریف اشیاء مرتبط با هر Concrete Factory با استفاده از اینترفیسهای مرتبط.
استفاده از Concrete Factory برای ایجاد اشیاء مرتبط.
خوبیها:
ایجاد قابلیت تعمیم: الگوی Abstract Factory به شما اجازه میدهد که کد قابل تعمیمی بسازید، زیرا شما میتوانید متغیرهای Concrete Factory را تعویض کنید بدون تغییر در کد بخشهای استفاده کننده.
جداسازی مفهومها: با استفاده از Abstract Factory، میتوانید فرایند ایجاد اشیاء را از بخش استفاده کننده جدا کنید و مفهومها را جدا کنید. این منجر به کاهش وابستگیها و افزایش قابلیت استفاده مجدد و قابلیت توسعه میشود.
بدیها:
محدودیت در تعداد محصولات: الگوی Abstract Factory محدودیتی در تعداد محصولاتی که توسط یک Concrete Factory ایجاد میشوند، ایجاد میکند. بنابراین، در صورت نیاز به افزودن محصولات جدید، نیاز است که Concrete Factory مربوطه تغییر کند و این ممکن است منجر به تغییر در سایر بخشهای کد نیز شود.
ارتباط با سایر الگوها:
الگوی Factory Method: الگوی Abstract Factory و Factory Method در این نقطه مشابه هستند که هر دو برای ایجاد اشیاء استفاده میشوند. اما تفاوت اصلی آنها در عملکرد است. در الگوی Abstract Factory، یک Abstract Factory تعیین میکند که چه Concrete Factory ایجاد شود و در نتیجه چه نوع محصولی تولید شود، در حالی که در Factory Method، این مسئولیت به سمت زیرکلاسها منتقل میشود.
الگوی Singleton: الگوی Abstract Factory میتواند با الگوی Singleton سازگاری داشته باشد. در صورتی که یک Concrete Factory به عنوان Singleton پیادهسازی شود، مطمئن میشویم که تنها یک نمونه از آن در کل برنامه وجود دارد و همه اشیاء متعلق به همان نمونه استفاده میکنند.
الگوی Decorator: الگوی Abstract Factory میتواند با الگوی Decorator سازگاری داشته باشد. با استفاده از الگوی Abstract Factory، میتوان یک Abstract Product را تولید کرده و سپس با استفاده از الگوی Decorator، به آن محصول قابلیتهای اضافی را اعمال کرد.
الگوی Bridge: الگوی Abstract Factory میتواند با الگوی Bridge نیز به خوبی ترکیب شود. با استفاده از الگوی Abstract Factory میتوانیم مجموعهای از محصولات مشابه را ایجاد کنیم و با استفاده از الگوی Bridge، بتوانیم آن محصولات را به طرق مختلف استفاده کنیم بدون تغییر در کد محصولات یا Abstract Factory.
الگوی Builder
قصد: اجازه میدهد ساختار پیچیدهای از یک شئ را به صورت مرحله به مرحله ساخت کنید، به طوری که یک شئ نهایی ایجاد شود که قابل تنظیم و قابل تغییر باشد.
مسئله: در برخی مواقع، ساخت یک شئ پیچیده ممکن است نیازمند انجام مراحل متعددی باشد. مثلاً فرض کنید یک سیستم سفارش آنلاین دارید و برای ایجاد یک سفارش، نیاز است که مشتری، محصولات را انتخاب کند، اطلاعات شخصی را وارد کند و تنظیمات دیگر را انجام دهد. این فرایند ساخت یک شئ پیچیده را میتوان با استفاده از الگوی Builder مدلسازی کرد.
راه حل: الگوی Builder پیشنهاد میکند که یک کلاس Builder جداگانه تعریف شود که مسئولیت ساختن شئ نهایی را دارد. این کلاس شامل متدهای مختلفی است که مراحل ساخت را نشان میدهند. همچنین، یک کلاس Director میتواند وظیفهی هدایت ساخت را بر عهده داشته باشد و کلاس Builder را با استفاده از متدهای مناسب به ترتیب فراخوانی کند. با این رویکرد، شئ نهایی به صورت تدریجی ساخته میشود و قابلیت تنظیم و تغییر آن در هر مرحله را دارد.
کاربردها:
ساخت آبجکتهای پیچیده: الگوی Builder به ما امکان میدهد تا آبجکتهایی پیچیده را به صورت مرحله به مرحله ساخت کنیم. این الگو برای ساختارهایی که نیاز به ترکیبی از شیها و مقادیر مختلف دارند، بسیار مناسب است.
پارامتریزه کردن ساخت آبجکت: با استفاده از الگوی Builder، میتوانیم پارامترهای مختلفی را برای ساخت آبجکت مشخص کنیم. این الگو به ما امکان میدهد تا در هر مرحله از ساخت آبجکت، پارامترهای مورد نیاز را به سازنده اضافه کنیم.
ایجاد روشهای ساخت مختلف: با استفاده از الگوی Builder، میتوانیم چندین روش ساخت مختلف برای یک آبجکت مشخص داشته باشیم. این الگو به ما امکان میدهد تا بر اساس نیازهای مختلف کاربران، روشهای ساخت متفاوتی را ارائه کنیم.
چگونگی پیادهسازی:
ایجاد کلاس Builder: یک کلاس Builder ایجاد میشود که مسئول ساختن آبجکت نهایی است. این کلاس شامل ویژگیهایی است که در آبجکت نهایی باید مقداردهی شوند و متدهایی برای تنظیم این ویژگیها.
تعریف کلاس Product: یک کلاس جداگانه برای آبجکت نهایی (کلاس Product) تعریف میشود. این کلاس شامل ویژگیهایی است که در آبجکت نهایی باید مقداردهی شوند.
مقادیر در Builder: در کلاس Builder، متدهایی برای تنظیم مقادیر مورد نیاز در آبجکت نهایی وجود دارد. هر متد، مقدار یکی از ویژگیهای آبجکت را تنظیم میکند. متدها به صورت زنجیرهای قابل استفاده هستند و میتوان چندین متد را پشت سر هم فراخوانی کرده و مقادیر مورد نیاز را تنظیم کرد.
متد build: در کلاس Builder، متدی به نام build تعریف میشود که آبجکت نهایی را ساخته و برمیگرداند. این متد، آبجکت نهایی را با استفاده از مقادیر تنظیم شده در متدهای دیگر ساخته و به کاربر برمیگرداند.
خوبیها:
امکان ساختاردهی منطقی و خوانا: الگوی Builder به کاربر اجازه میدهد تا به صورت مرحله به مرحله، مقادیر مورد نیاز را برای ساخت آبجکت تنظیم کند، که این باعث ساختاردهی منطقیتر و خواناتر کد میشود.
انعطافپذیری در ساختار ساختگر: الگوی Builder به کاربر اجازه میدهد تا با استفاده از متدهای مختلف در کلاس Builder، روشهای مختلفی برای ساخت آبجکت را اعمال کند و مقادیر دلخواه خود را تنظیم کند. این انعطافپذیری به کاربر امکان میدهد تا آبجکت را به شکل دقیقی که نیاز دارد، ساختاردهی کند.
جداسازی رابطهی ساخت و نتیجهی ساخت: با استفاده از الگوی Builder، کاربران قادر خواهند بود رابطهی ساخت (با استفاده از کلاس Builder) را از نتیجهی ساخت (آبجکت نهایی) جدا کنند. این امر باعث میشود که ساخت آبجکت به شکلی مستقل از نتیجهی ساخت انجام شود و کاربر قادر خواهد بود مقادیر را در هر مرحله تنظیم کند و به راحتی مقداردهی مجدد را انجام دهد.
بدیها:
محدودیت در انعطافپذیری: الگوی Builder در حالت عمومی، برای ساختارهای ثابت و مشخص استفاده میشود. در صورتی که ساختار شئ قابل تغییر و انعطافپذیر باشد و بتواند به صورت پویا و در زمان اجرا تعیین شود، ممکن است الگوی Builder نسبت به نیازهای پروژه کمتر انعطافپذیری داشته باشد.
ارتباط با سایر الگوها:
الگوی Factory: الگوی Factory و الگوی Builder دو الگوی مکمل هستند. الگوی Factory به کاربر این امکان را میدهد تا یک شئ را بدون آگاهی از مراحل دقیق ساخت آن ایجاد کند، در حالی که الگوی Builder بر روی مراحل ساخت تمرکز دارد. با استفاده از این دو الگو به صورت ترکیبی، میتوان یک فرآیند ساخت پیچیده را سادهتر و قابل فهمتر کرد.
الگوی Decorator: الگوی Decorator به کاربر این امکان را میدهد تا به یک شئ ویژگیهای اضافی را به صورت پویا و در زمان اجرا اضافه کند. با استفاده از الگوی Builder و Decorator، میتوان یک شئ پیچیده را مرحله به مرحله ساخت و هر مرحله را با ویژگیهای اضافی تزئین کرد.
الگوی Composite: الگوی Composite به کاربر این امکان را میدهد تا یک ساختار درختی از شئها را بسازد. با استفاده از الگوی Builder و Composite، میتوان یک ساختار پیچیده از شئها را متشکل از زیرشئهای سادهتر و مستقل ساخت.
الگوی Strategy: الگوی Strategy به کاربر این امکان را میدهد تا یک روش خاص از عملکرد را در زمان اجرا انتخاب کند. با استفاده از الگوی Builder و Strategy، میتوان مراحل ساخت یک شئ را با استفاده از روشهای مختلفی تعیین کرد و به صورت پویا در زمان اجرا استراتژی مربوطه را انتخاب کرد.
الگوی Observer: الگوی Observer به کاربر این امکان را میدهد تا تغییراتی که در یک شئ رخ میدهد را پیگیری کند. با استفاده از الگوی Builder و Observer، میتوان در هر مرحله از ساخت یک شئ تغییرات آن را پیگیری کرد و بر اساس آن عمل کرد.
الگوی Singleton
قصد: این الگو قصد دارد تا بستری را فراهم آورد تا از یک کلاس، تنها یک نمونه ایجاد شود. در دنیای حقیقی نیز بسیار مشاهده میشود که مواردی هستند که تنها یک نمونه از آنها وجود دارد و نحوه ارجاع به آن مشخص است. مثلا رئیس جمهور یک کشور، برای همه شهروندان یک شخص است و امکان این که هر شهروند یک رئیس جمهور برای خود بسازد(!) وجود ندارد.
مسئله: الگوی تکینگی مشکل تولید نمونه یک کلاس تنها به یک عدد را حل میکند اما به طبع حل آن مشکل جدید ظهور میکند و آن هم این است که نحوه دسترسی به این کلاس جدید چگونه باید باشد. این مشکل نیز ذیل این الگو حل خواهد شد. بنابراین به تفضیل مشکلاتی که این الگو قصد حل آنها را دارد عبارت است از:
ساخت تنها یک نمونه از کلاس: در بسیاری از موارد کلاسها نقش مدیریتی دارند و نیاز است که تنها یک نمونه از آنها ساخته شود. به عنوان مثال کلاس واسط بین برنامه و یک منبع محدود، مانند چاپگر و یا پایگاه داده را در نظر بگیرید. باید که ارتباط با این منابع با قفلهای اشتراکی و اختصاصی محدود شود. بنابراین باید ساخت این کلاس تنها به یک نسخه محدود شود تا همه کنترلهای لازم توسط این کلاس صورت گیرد.
دسترسی به تک نمونه ساخته شده از یک کلاس: برای دسترسی به نمونه ساخته شده از کلاس نیاز است که مرجعی در دسترس برای نمونه ساخته شده وجود داشته باشد تا همگی به آن دسترسی داشته باشند.
این مشکل میتواند خود یک مشکل مستقل باشد. ممکن است که محدودیتی در ساخت تعداد نمونههای یک کلاس وجود نداشته باشد ولی نیاز باشد تا همه تنها از یک نسخه استفاده کنند. در این صورت پاس دادن کلاس به عنوان آرگومان در تمام توابع کاری دشوار خواهد بود.
راه حل: راه حل این مشکل ساده است. باید دسترسی به متد سازنده کلاس را تنها به خود کلاس محدود کرد چرا که صرف فراخوانی این تابع حتی اگر با خطا همراه باشد، تخصیص حافظه به همراه خواهد داشت. در مقابل باید یک متد به صورت عمومی ایجاد شود که نمونه ساخته شده را برگرداند. این متد باید در اولین فراخوانی نمونه را ایجاد کرده و از فراخوانیهای بعدی آن نمونه را برگرداند. برای ذخیره نمونه ساخته شده از یک فیلد استاتیک داخل کلاس استفاده میشود.
کاربردها:
این الگو زمانی مورد استفاده قرار میگیرد که تنها میخواهیم یک نمونه از یک کلاس داشته باشیم که در اختیار تمام کاربران قرار بگیرد. استفاده از این الگو دسترسی ساخت آن کلاس را به هر جهت محدود میکند.
این الگو زمانی مورد استفاده قرار میگیرد که میخواهیم بر روی مقادیر گلوبال کنترل سختگیرانه داشته باشیم و آنها را نه به صورت خام، بلکه توسط یک کلاس کنترل کنیم.
چگونگی پیادهسازی: جزئیات پیادهسازی تا حدی در بخش راه حل ارائه شد. در اینجا به یک قطعه کد از یک کلاس که به این صورت پیادهسازی شده است اکتفا میکنیم.
خوبیها:
میتوان تنها یک نسخه از یک کلاس داشت.
میتوان دسترسی عمومی به نسخه ایجاد شده از کلاس ایجاد کرد.
ساخت نمونه به صورت تنبل است. زمانی انجام میشود که درخواست آن انجام شده است.
بدیها:
قاعده تکمسئولیتی را نقض میکند چرا که همزمان دو مشکل را رفع میکند.
این الگو مشکلات طراحی را میتواند پنهان کند، چرا که دسترسی عمومی به یک کلاس بوجود میآورد و زوجیت بالا بین اجزا ممکن است بوجود آورد.
برای پردازشهای موازی نیاز است که تمهیداتی صورت گیرد تا تداخل بوجود نیاید.
برای تست واحد شرایط بسیار پیچیده میشود چرا که امکان جعل آن به سادگی فراهم نیست.
ارتباط با سایر الگوها:
الگوی Facade: آن را میتوان به این الگو کاهش داد چرا که عموما تنها یک Facade کفایت میکند.
الگوی Flyweight: در آن با جداسازی اطلاعات مشترک در یک کلاس Singleton میتوانیم پیادهسازی را انجام دهیم. البته در این حالت الزامی است که تمام اطلاعات مشترک را بتوان در یک نسخه جمعآوری کرد.
الگوهایی از قبیل Abstract Factory و Builder: آنها را میتوان در این قالب پیادهسازی کرد.
الگوهای ساختاری یا Structural
این الگوها مسائل مربوط به ترکیب کلاسها و اشیاء برای ایجاد ساختارهای بزرگتر را در نظر میگیرند، طوری که انعطافپذیری خوبی داشته باشند و بهینه باشند. در ادامه چند نمونه از مهمترین الگوهای ساختاری ارائه خواهند شد.
الگوی Composite
قصد: اجازه میدهد تعدادی شئ را در یک ساختار درختی تعریف کرد و با درخت حاصل به عنوان یک شئ با اعمال مورد نظر برخورد کرد.
مسئله: استفاده از این الگو فقط زمانی منطقی است که ساختاری درختی در آن مشاهده شود. فرض کنید میخواهید یک موتور بازیسازی طراحی کنید و قصد دارید به کاربر اجازه دهید که اشیاء را در یک ساختار سلسله مراتبی تعریف کنند و روی آنها اعمالی انجام دهند، مثل جا به جا کردن همراه هم در یک محیط. به عبارتی، تعدادی کلاس شئ پایه در موتور خود دارید و میخواهید به کمک کلاسهای دستهبندی سلسله مراتب ایجاد کنید. توجه کنید که یک دستهبندی میتواند شامل دستهبندیهای دیگر نیز بشود. چگونه باید در این ساختار سلسله مراتبی اعمال را روی تمام اشیاء داخل آن پیاده کنیم؟
راه حل: الگوی Composite پیشنهاد میکند که برای کلاسهای پایهی اشیاء و کلاسهای دستهبندی، یک رابط مشترک تعریف شود که برای هر کدام از اعمالی که در نظر داریم یک تابع در نظر گرفته است. این توابع درواقع اگر برای یک شئ پایه صدا زده شوند، صرفاً عمل مذکور را روی آن اجرا میکنند، اما اگر روی یک شئ دستهبندی اجرا شود، به ترتیب روی تمام اشیاء داخل آن فراخوانی میشود. بدین صورت، نه تنها ساختار درختی را به کمک اشیاء دستهبندی ایجاد میشود، (چون که لیستی از اشارهگرهای از نوع این رابط در خود دارند) بلکه اجرای عمل مذکور در ساختار درختی پایینتر میرود و روی تمام اشیاء اعمال میشود. بزرگترین مزیت این ایده این است که نیازی به دانستن ذات اشیاء داخل سلسله مراتب نیست، چرا که فقط با استفاده از رابط مشترک با اشیاء تعامل صورت میگیرد و خود اشیاء تصمیم میگیرند که چه رفتاری از خود به نمایش بگذارند.
کاربردها:
زمانی که باید یک ساختار درخت مانند را پیادهسازی کنید از این الگو استفاده کنید.
زمانی که میخواهید کاربر با اشیاء ساده و پیچیده به صورت یکنواخت برخورد کند از این الگو استفاده کنید.
چگونگی پیادهسازی:
از این که مدل اصلی برنامهی شما میتواند به صورت یک درخت نمایش داده شود اطمینان حاصل کنید. آن را به المانهای ساده و المانهای دستهبندی بشکنید و به خاطر داشته باشید که المانهای دستهبندی باید بتوانند هم شامل المانهای ساده شوند و هم المانهای دستهبندی دیگر.
رابط مشترک را با لیستی از اعمال که برای المانهای ساده و المانهای دستهبندی منطقیاند تعریف کنید.
کلاسهای برگ را تعریف کنید که المانهای ساده را نمایش دهند.
کلاس دستهبندی را تعریف کنید و داخل آن یک لیست از المانها از نوع رابط مشترک در نظر بگیرید. بدین صورت، هم المانهای ساده و هم المانهای دستهبندی را میتوان در آن ذخیره نمود. در پیادهسازی توابع کلاس دستهبندی حواستان باشد که اکثر وظایف را به المانهای فرزند باید محول کنید.
در نهایت، اعمال افزودن و حذف کردن المان به یک المان دستهبندی را در کلاس دستهبندی تعریف کنید. تعریف این اعمال میتواند در رابط مشترک انجام شود و استفاده از ساختار درختی را برای کاربران ساده کند، اما اصل Interface Segregation را نقض میکند.
خوبیها:
با ساختارهای پیچیدهی درختی راحتتر میتوان کار کرد، چرا که میتوان از توابع بازگشتی و مفهوم چندریختی استفاده کرد.
چون میتوان بدون تغییر دادن پیادهسازیهای موجود المانهای جدید به سیستم و ساختار درختی اضافه و کم کرد، اصل Open/Closed را رعایت کرده است.
بدیها:
تعریف یک رابط مشترک برای اشیائی که خیلی با هم تفاوت دارند سخت است. ممکن است مجبور شوید که رابط را خیلی تعمیم دهید و شامل اعمال مختلف کنید، که باعث میشود فهم آن سخت شود و اصل Interface Segregation کمی نقض شود.
ارتباط با سایر الگوها:
الگوی Builder: برای ایجاد درختها میتوان از الگوی Builder استفاده کرد، چرا که منطق آن را میتوان بازگشتی ساخت.
الگوی Chain of Responsibility: در کنار این الگو زیاد استفاده میشود، چرا که میتوان یک درخواست را به یک برگ از درخت داد تا گام به گام تا ریشه بالا برود و اجرا شود.
الگوی Iterator: برای پیمایش درختهای Composite میتوان از الگوی Iterator استفاده کرد.
از الگوی Visitor میتوان برای اجرای یک عملیات روی کل درخت استفاده کرد.
برای مصرف بهینهی حافظه میتوان رئوس برگ مشترک را به کمک الگوی Flyweight تعریف کرد.
الگوی Decorator: این الگو به دلیل بازگشتی بودن ساختاری مشابه با آن دارد. درواقع الگوی Decorator مثل یک Composite است که هر رأس آن فقط یک بچه دارد. همچنین، به رئوس وظایف جدید اضافه میکند، در حالی که Composite فقط تجمیع نتایج فرزندان را میگیرد. با این حال، این دو الگو میتوانند با همکاری کنند و مثلاً یک شئ در ساختار درخت Composite توسط یک Decorator توسعه داده شود.
الگوی Prototype: طراحیهایی که از الگوهای Composite و Decorator استفاده میکنند میتوانند از این الگو به خوبی بهره ببرند. این الگو اجازه میدهد که ساختارهای پیچیده مانند درختان کپیبرداری شوند، بدون این که از اول ساخته شوند.
الگوی Decorator
قصد: امکان اضافه کردن قابلیتها و ویژگیهای جدید به یک شیء بدون تغییر ساختار اصلی آن.
مسئله: مسئلهای که این الگو حل میکند، امکان اضافه کردن ویژگیها به یک شیء در زمان اجرا بدون نیاز به تغییر کدهای موجود است. این الگو به ما اجازه میدهد ویژگیها را به شیء اصلی اضافه کرده و برخی از تغییرات را بدون تأثیرگذاری بر کد مشتریان انجام دهیم.
راه حل:
الگوی Decorator برای اضافه کردن قابلیتها و ویژگیهای جدید به یک شیء بدون تغییر ساختار اصلی آن طراحی شده است. این الگو امکان میدهد تا به یک شیء موجود قابلیتهای جدیدی را به صورت پویا و در زمان اجرا اضافه کنیم.
در این الگو، شیء اصلی را با استفاده از کلاسهای تغلیظ کننده (Decorator) پوشش میدهیم. هر کلاس تغلیظ کننده، از یک تغلیظ کننده قبلی مشتق میشود و قابلیتهای جدیدی را به شیء اصلی اضافه میکند. به این ترتیب، تغلیظ کنندهها قابلیتها را به صورت پشت سر هم به شیء اضافه میکنند و یک شیء تغلیظ شده با تمامی ویژگیهای اضافه شده بدست میآید.
ویژگی مهم این الگو این است که میتوانیم به طور پویا و در زمان اجرا ویژگیها را به شیء اضافه کنیم. با اضافه کردن تغلیظ کنندهها به صورت پشت سر هم، میتوانیم ترکیبهای مختلفی از قابلیتها را بسازیم و شیء را با ویژگیهای دلخواه تغلیظ کنیم.
کاربردها: الگوی Decorator در زمینههای مختلفی از جمله برنامهنویسی شیءگرا کاربردهای متنوعی دارد. برخی از کاربردهای رایج این الگو عبارتند از:
افزودن ویژگیها و قابلیتها به شیء: با استفاده از الگوی Decorator، میتوان به شیءهای موجود قابلیتها و ویژگیهای جدیدی اضافه کرد، بدون اینکه به تغییر ساختار آنها نیاز باشد. این الگو به برنامهنویسان اجازه میدهد قابلیتهایی را به صورت پویا و در زمان اجرا به شیء اضافه کنند.
تغییر عملکرد شیء: با استفاده از تغلیظ کنندهها، میتوان عملکرد شیء را تغییر داد. به عنوان مثال، میتوان با استفاده از یک تغلیظ کننده، متدهایی را به شیء اضافه کرد که قبلاً وجود نداشته و عملکرد شیء را بهبود دهد.
استفاده از شیءهای چندلایه: با استفاده از تغلیظ کنندهها میتوان شیءهای چندلایه را ساخت. به این ترتیب، قابلیتهای جدید به صورت لایه به لایه به شیء اضافه میشود و امکان ترکیب و استفاده از این قابلیتها در قالب یک شیء تغلیظ شده فراهم میشود.
چگونگی پیادهسازی:
پیادهسازی الگوی Decorator میتواند به شکلهای زیر انجام شود:
۱. ایجاد یک رابطه پایه: ابتدا یک رابطه پایه برای شیء اصلی و تمامی تغلیظ کنندهها ایجاد میکنیم. این رابطه میتواند یک رابطه یا یک کلاس انتزاعی باشد که تعیین کننده متدهای عمومی شیء اصلی است.
۲. پیادهسازی شیء اصلی: سپس شیء اصلی را پیادهسازی میکنیم و متدهای آن را به صورت پیشفرض پیادهسازی میکنیم.
۳. پیادهسازی تغلیظ کنندهها: هر تغلیظ کننده را به عنوان یک کلاس مشتق از رابطه پایه ایجاد میکنیم. در این کلاس، علاوه بر پیادهسازی متدهای رابطه پایه، میتوانیم قابلیتها و ویژگیهای جدید را اضافه کنیم. به طور معمول، هر تغلیظ کننده درون خود یک شیء اصلی یا یک تغلیظ کننده قبلی را نگهداری میکند.
۴. تغلیظ تغلیظ کنندهها: میتوانیم تغلیظ کنندهها را به صورت پشت سر هم ترکیب کنیم تا قابلیتها و ویژگیهای جدید را به شیء اضافه کنیم. در این صورت، خروجی یک شیء تغلیظ شده با تمام قابلیتهای اضافه شده خواهد بود.
۵. استفاده از شیء تغلیظ شده: در نهایت، میتوانیم از شیء تغلیظ شده در برنامه خود استفاده کنیم و به ویژگیها و قابلیتهای اضافه شده دسترسی داشته باشیم.
خوبیها:
۱. افزایش قابلیت انعطافپذیری: الگوی Decorator به ما امکان میدهد تا به صورت پویا و در زمان اجرا قابلیتها و ویژگیهای جدید را به یک شیء اضافه کنیم بدون نیاز به تغییر ساختار اصلی آن. این باعث میشود که بتوانیم شیء را به نیازهای متغیر مشتریان و محیط اطراف سازگار کنیم.
۲. استفاده مجدد و قابلیت ترکیب: با استفاده از الگوی Decorator، قابلیتها و ویژگیهای جدید را به شیء اضافه کنیم و در نتیجه شیء قابل استفاده مجددی خواهد بود. همچنین، میتوانیم تغلیظ کنندهها را به صورت پشت سر هم ترکیب کنیم و ترکیبهای مختلفی از قابلیتها را بسازیم.
۳. جدا سازی مسئولیتها: با استفاده از الگوی Decorator، قابلیتها و ویژگیهای مختلف را در تغلیظ کنندههای جداگانه جداسازی میکنیم. این باعث میشود که هر تغلیظ کننده مسئولیت خاص خود را داشته باشد و تغییر در یک تغلیظ کننده به سایر بخشها تأثیری نداشته باشد.
بدیها: به طور کلی، الگوی Decorator دارای معایب زیر است:
۱. افزایش پیچیدگی: استفاده از الگوی Decorator ممکن است باعث افزایش پیچیدگی کد شود. زمانی که تعداد زیادی تغلیظ کننده و ترکیبهای آنها وجود دارد، مدیریت کد و درک عملکرد شیء سختتر میشود.
۲. افزایش هزینه: پیادهسازی الگوی Decorator ممکن است به هزینههای اضافی منجر شود. زمان و تلاش بیشتری برای پیادهسازی و توسعه الگو و تغییر در ساختار کد مورد نیاز است.
۳. اضافه کردن سطوح تغلیظ: استفاده از الگوی Decorator میتواند به اضافه کردن سطوح تغلیظ و تغییرات پیچیده در ساختار شیء منجر شود، که ممکن است برای برخی تیمها و توسعهدهندگان دشوار باشد.
۴. تأخیر در اجرا: استفاده از تغلیظ کنندهها و ترکیبهای آنها ممکن است به تأخیر در اجرای برنامه منجر شود، زیرا هر تغلیظ کننده ممکن است عملیات اضافی را به شیء اضافه کند که زمان بیشتری را مصرف میکند.
۵. احتمال ایجاد تعارض: استفاده نادرست از الگوی Decorator میتواند منجر به تعارضهایی مانند تداخل بین تغلیظ کنندهها یا تداخل در عملکرد شیء اصلی شود.
ارتباط با سایر الگوها:
الگوی Decorator در ارتباط با سایر الگوها میتواند به صورت مجزا استفاده شود یا با سایر الگوها ترکیب شود. در زیر توضیحی اجمالی درباره ارتباط الگوی Decorator با برخی از الگوهای معروف دیگر آورده شده است:
الگوی Composite (ترکیبی): الگوی Composite به ما امکان میدهد تا شیءها را به صورت ساختار درختی ترکیب کنیم و به آنها به صورت یک شیء واحد دسترسی داشته باشیم. Decorator در اینجا میتواند برای افزودن ویژگیها و قابلیتها به شیءهای Composite استفاده شود، به طوری که هر تغلیظ کننده ویژگیهای اضافی را به یک شیء Composite اضافه کند.
الگوی Adapter (تطبیقی): الگوی Adapter برای تطبیق و اتصال دو واسط یا کلاس با ساختارهای متفاوت استفاده میشود. Decorator میتواند با الگوی Adapter ترکیب شود تا ویژگیها و قابلیتهای جدید را به یک شیء Adapter اضافه کند.
الگوی Strategy (استراتژی): الگوی Strategy برای تعیین و استفاده از الگوهای مختلف عملکرد استفاده میشود. Decorator میتواند با الگوی Strategy ترکیب شود تا ویژگیها و قابلیتهای جدید را به یک استراتژی اضافه کند، بدون تغییر در عملکرد اصلی استراتژی.
الگوی Proxy (پروکسی): الگوی Proxy برای ایجاد یک پروکسی برای دسترسی و کنترل به یک شیء استفاده میشود. Decorator میتواند با الگوی Proxy ترکیب شود تا ویژگیها و قابلیتهای جدید را به یک پروکسی اضافه کند و در عین حال دسترسی به شیء اصلی را مدیریت کند.
الگوی Proxy
قصد: هدف الگوی Proxy یا نماینده، ایجاد یک پروکسی برای کنترل و دسترسی به یک شیء است. این الگو به ما اجازه میدهد که از طریق پروکسی، کارهایی مانند کنترل دسترسی، ایجاد یک لایهٔ محافظتی، لود بالا و دسترسی به شیء اصلی را مدیریت کنیم.
مسئله:
مسئلهای که الگوی Proxy حل میکند، مربوط به مدیریت دسترسی به یک شیء است. در برخی موارد، شاید نخواهیم کاربران یا اشیاء دیگر به صورت مستقیم به شیء اصلی دسترسی داشته باشند و نیازمند یک لایهٔ محافظتی یا کنترلی باشند.
راه حل:
برای حل مسئله، الگوی Proxy به صورت یک پروکسی (Proxy) برای شیء اصلی ایجاد میشود. این پروکسی همان رابطهٔ شیء اصلی را پیادهسازی میکند و عملکردی شبیه به شیء اصلی دارد. پروکسی قابلیتهای اضافی نیز میتواند ایجاد کند و برای انجام وظایفی مانند کنترل دسترسی، ثبت وقایع، اجرای تأخیری (Lazy Loading) و کش کردن (Caching) استفاده شود. هنگامی که کاربر یا شیء دیگری به جای شیء اصلی، به پروکسی دسترسی میکند، پروکسی میتواند تصمیم بگیرد که از دسترسی به شیء اصلی خودداری کند یا به آن دسترسی بدهد.
کاربردها:
کنترل دسترسی: الگوی Proxy میتواند برای کنترل دسترسی به یک شیء استفاده شود. با استفاده از پروکسی، میتوان محدودیتها و مجوزهای دسترسی را برای شیء اصلی تعیین کرده و دسترسی به آن را مدیریت کرد.
ایجاد لایهٔ محافظتی: الگوی Proxy میتواند برای ایجاد یک لایهٔ محافظتی بر روی شیء اصلی استفاده شود. پروکسی میتواند قابلیتهای اضافی مانند رمزنگاری و رمزگشایی، ثبت وقایع و اعتبارسنجی را برای شیء اصلی فراهم کند.
لود بالا و بهینهسازی عملکرد: الگوی Proxy میتواند برای مدیریت لود بالا و بهینهسازی عملکرد استفاده شود. پروکسی میتواند نتایج قبلی را کش کند و درخواستها را به شیء اصلی فقط در صورت نیاز ارسال کند.
ایجاد نمایی (Virtual Proxy): با استفاده از الگوی Proxy، میتوان یک نمایی از شیء اصلی ایجاد کرد. به جای ایجاد و اینیشیالیزهکردن یک شیء اصلی، پروکسی نمایی ایجاد میکند که در صورت نیاز به شیء اصلی دسترسی مییابد.
پیشبینی ویژگیهای آینده: الگوی Proxy میتواند برای پیشبینی ویژگیهای آینده یک شیء استفاده شود. در این حالت، پروکسی میتواند قابلیتها و ویژگیهایی را اضافه کند که ممکن است در آینده نیاز شود.
خوبیها:
کاربر بدون داشتن دانشی در مورد سرویس میتواند از آن استفاده کند.
دورهی عمر سرویس را میتوان مستقل از کاربران مدیریت کرد.
شئ Proxy حتی وقتی خود سرویس خراب و غیرفعال است کار میکند.
چون که میتوان به راحتی Proxy جدید به سیستم اضافه کرد، اصل Open/Closed رعایت شده است.
بدیها:
پیادهسازی میتواند پیچیده شود به دلیل کلاسهای زیاد اضافه شده.
پاسخ سرویس میتواند دچار تأخیر شود.
ارتباط با سایر الگوها:
الگوی Decorator: الگوی Proxy و Decorator از یکدیگر جدا و به صورت مجزا هدفمندی میکنند. در حالی که الگوی Decorator برای افزودن ویژگیها و قابلیتهای جدید به یک شیء استفاده میشود، الگوی Proxy برای مدیریت دسترسی و ایجاد یک لایهٔ محافظتی بر روی شیء استفاده میشود. با این حال، ممکن است در برخی موارد از این دو الگو به صورت ترکیبی استفاده شود تا هم طرحوارهٔ ویژگیها را افزایش دهد و هم مدیریت دسترسی را بهبود بخشد.
الگوی Adapter: الگوی Proxy و Adapter در برخی موارد ممکن است با هم ترکیب شوند. وقتی یک Adapter به یک شیء نیاز داریم تا واسطهای بین دو واسط یا کلاس با ساختارهای متفاوت باشد، میتوانیم از الگوی Proxy برای ایجاد این واسطه استفاده کنیم. در این حالت، پروکسی نمیتواند تنها دسترسی را مدیریت کند بلکه میتواند قابلیتها و رفتارهای اضافی را به Adapter اضافه کند.
الگوهای رفتاری یا Behavioral
این الگوها مسائل مربوط به عملکرد و تعامل بین اشیاء و نیز تقسیم وظایف بین آنها را در نظر میگیرد. در ادامه چند نمونه از مهمترین الگوهای رفتاری ارائه خواهند شد.
الگوی Iterator
قصد: قابلیت پیمایش یک دادهساختار دلخواه را ارائه میدهد، بدون این که ساختار درونی آن را به بیرون نشان دهد.
مسئله: مجموعهها از مرسومترین دادهساختارها در برنامهنویسیاند که تعدادی شئ را در خود نگه میدارند. پیادهسازی این مجموعهها اما میتواند بسته به کاربرد با ساختارهای پیچیدهای انجام شود و لازم است که کاربران بتوانند بدون این که از درون این ساختارها خبر داشته باشند و یا کد خود را به آنها وابسته کنند، به المانهای داخل آنها دسترسی داشته باشند و آن را پیمایش کنند. همچنین، برای ساختاری مانند یک درخت میتوان روشهای پیمایش متعددی را متصور شد. در نتیجه، لازم است که بتوان با گذر زمان الگوریتمهای پیمایشهای جدید اضافه کنیم، کاری که در حالت عادی پیادهسازی این دادهساختارها را از هدف اصلی خود دور میکند: ذخیرهی بهینهی داده.
راه حل: الگوی Iterator پیشنهاد میکند که الگوریتمهای پیمایش در قالب کلاسهای جدا استخراج شوند و از آنها برای پیمایش استفاده شود. این کلاسها علاوه بر پیادهسازی خود الگوریتمها، دادگان مربوط به حالت فعلی پیمایش را در خود ذخیره میکنند تا بتوان با هر زمانبندیای پیمایش را انجام داد. این باعث میشود که بتوان همزمان چند پیمایشگر تعریف کرد و آنها را به صورت موازی استفاده کرد تا چند بار از روی دادگان ساختار رد بشویم. اشیاء پیمایشگر معمولاً یک تابع واحد برای پیمایش دارند که از آن استفاده میشود تا روی ساختار حرکت کرد و المانهای بعدی را دریافت کرد، تا وقتی که دیگر المان جدیدی خروجی داده نشود. تمام پیمایشگرها باید رابطی یکسان را پیاده کنند تا کاربران بتوانند بدون مشکل از هر دادهساختار و هر الگوریتم پیمایشی استفاده کنند، به شرط این که یک پیمایشگر در دسترس باشد. لازم به ذکر است که پیمایشگر مناسب از خود دادهساختار آن درخواست میشود و در نتیجه، لازم است که یک رابط برای مجموعهها نیز تعریف شود که تابع ساختن پیمایشگر را در خود داشته باشد.
کاربردها:
زمانی که مجموعهی شما ساختار پیادهسازی پیچیدهای دارد که از کاربر مخفی شود از این الگو استفاده کنید.
از این الگو برای کاهش تکرار شدن کد پیمایش خود استفاده کنید.
زمانی که میخواهید برنامهیتان از دادهساختارهای مختلف پشتیبانی کند و یا نوع دادهساختار از پیش مشخص نیست، از این الگو استفاده کنید.
چگونگی پیادهسازی:
رابط مشترک پیمایشگرها را ایجاد کنید. این رابط باید حداقل تابعی برای دریافت المان بعدی داشته باشد، اما میتواند شامل مواردی مثل بررسی اتمام پیشمایش نیز بشود.
رابط مشترک مجموعهها را تعریف کنید و یک تابع برای دریافت پیمایشگرها تعریف کنید. خروجی این تابع از نوع رابط کلی پیمایشگرها باشد. توجه کنید که میتوان چندین تابع به این شکل تعریف کرد که مربوط میشوند به دستههای مختلفی از پیمایشگرها.
کلاسهای پیمایشگر را تعریف کنید. یک شئ پیمایشگر باید با دقیقاً یک مجموعهی قابل پیمایش متناظر باشد، که عموماً از طریق تابع سازندهی آن تنظیم میشود.
رابط مجموعهها را به دادهساختارهای خود اضافه کنید تا هر کدام بتوانند پیمایشگر مناسب خود را به شیوهای ساده به کاربران ارائه دهند. لازم به ذکر است که شئ مجموعه باید خودش را به تابع سازندهی پیمایشگر بدهد تا بتواند تنظیمات مناسب را انجام.
تمام بخشهایی از کد کاربر که پیمایش انجام میدهند را با این الگو جایگزین کنید.
خوبیها:
استفاده از این الگو از اصل Single Responsibility پیروی میکند و در نتیجه، پیادهسازی کد کاربر را تمیزتر میکند.
چون که به راحتی و بدون تغییر کد میتوان الگوریتمهای پیمایش جدید و دادهساختارهای جدید اضافه کرد، اصل Open/Closed را رعایت کرده است.
چون هر پیمایشگر تمام اطلاعات لازم برای پیمایش را در خود دارد، میتوان یک دادهساختار را با استفاده از چند پیمایشگر به صورت موازی پیمود.
بنا به دلیلی مشابه، میتوان پیمایش را با تأخیر و زمانبندی دلخواه انجام داد.
بدیها:
استفاده از این الگو زمانی که دادهساختارها سادهاند معقول نیست.
استفاده از این الگو برای پیمایش بعضی از ساختارها میتواند کندتر از دسترسی مستقیم به دادههای آن باشد.
ارتباط با سایر الگوها:
الگوی Composite: از این الگو برای پیمایش ساختارهای حاصل از الگوی Composite استفاده نمود.
الگوی Factory Method: میتوان از آن برای ایجاد زیرکلاسهای دادهساختارهایی استفاده کرد که انواع مختلف پیمایشگر مناسب برای خود را میسازند.
الگوی Memento: با استفاده از آن میتوان حالت فعلی جستجو را ذخیره نمود و در صورت نیاز جستجو را به حالات قدیم بازگرداند.
الگوی Visitor: با استفاده از آن در کنار یک پیمایشگر میتوان عملیاتی را روی مجموعهای از المانها اجرا کرد، اگر ساختار و نوع المانهای مذکور با هم متفاوت باشد.
الگوی Strategy
قصد: خانوادهای از الگوریتمها را تعریف میکند و هر کدام را در یک کلاس جدا میگذارد طوری که بتوان اشیاء آن را بر حسب شرایط و نیاز با هم جایگزین کرد.
مسئله: فرض کنید که میخواهید یک برنامهی مسیریابی روی نقشه طراحی کنید. انتظار میرود که برنامهی شما بتواند برای خودروها مسیریابی انجام دهد، اما با گذر زمان تصمیماتی مبنی بر افزودن مسیریابی برای حمل و نقل عمومی، پیاده، دوچرخه و … گرفته میشود، طوری که کاربر بتواند با توجه به نیاز خود از بین آنها انتخاب کند. چون که تغییرات در پروژه کم کم ایجاد میشوند، تغییر دادن سادهی پیادهسازی داخل کلاسهای پایهی نرمافزار میتواند باعث گره خوردن الگوریتمها به هم و نیز به کد نمایش نقشه شود. در نتیجه، نه تنها نگهداری و ایجاد تغییر در این الگوریتمها خیلی سخت میشود، بلکه کار تیمی نیز سختتر میشود، چرا که فهم کد برای عضوهای جدید بسیار دشوار خواهد بود.
راه حل: الگوی Strategy پیشنهاد میکند که این الگوریتمها را از کلاسی که در آن تعریف شدهاند بیرون بکشید و در قالب کلاسهایی که یک رابط مشترک دارند پیاده کنید. بدین صورت، برای استفاده از آنها در کلاس زمینه یا Context کافی است یک اشارهگر از نوع رابط تعریف شده در نظر بگیرد و مسئولیت اجرای الگوریتمها را شئ مورد نظر محول کند، بلکه عموماً توسط ورودی کاربر از انتخاب الگوریتم مطلع میشود و این کار را انجام میدهد. توجه کنید که انتخاب الگوریتم بر عهدهی کلاس زمینه نیست. کلاس زمینه حتی از ساختار و تفاوت الگوریتمها خبر ندارد و صرفاً آنها را از طریق رابط مشترکشان که فقط یک تابع برای فعال کردن دارد، اجرا میکند. بدین صورت، کلاس زمینه از الگوریتمها مستقل میشود و میتواند الگوریتمها را بر حسب نیاز تغییر دهد. در نتیجه، کلاس نمایش نقشه بدون این که به الگوریتم مسیریابی اهمیتی دهد میتواند الگوریتم مورد نظر کاربر را اجرا کند.
کاربردها:
زمانی که میخواهید چندین نسخه الگوریتم برای انجام یک کار واحد تعریف کنید و بین آنها انتخاب کنید، از این الگو استفاده کنید.
زمانی که چندین کلاس مشابه دارید که فقط در یک الگوریتم متفاوتند از این الگو استفاده کنید. این الگو اجازه میدهد که اجزاء متغیر این کلاسها را بیرون بکشید و آنها را به یک کلاس مشترک تبدیل و ادغام کنید.
برای جدا کردن منطق اقتصادی و Business Logic یک کلاس از جزئیات پیادهسازی الگوریتمهای آن، زمانی که منطق اقتصادی مهمتر است، از این الگو استفاده کنید.
زمانی که کلاسی ساختاری بزرگ برای انتخاب یک الگوریتم برای اجرا کردن دارد، از این الگو استفاده کنید.
چگونگی پیادهسازی:
در کلاس زمینه الگوریتمهایی که ممکن است بر حسب نیاز عوض شوند را پیدا کنید. این میتواند در قالب مجموعهای از شرطها باشد که یک رویکرد را برای اجرا انتخاب میکند.
رابط مشترک همهی الگوریتمهای مورد نظر را طراحی کنید.
هر کدام از الگوریتمها را در کلاس خودشان پیاده کنید، طوری که از رابط تعریف شده استفاده کنند.
در کلاس زمینه اشارهگری از نوع رابط الگوریتمها تعریف کنید و برای آن یک عملگر Setter بسازید. در صورتی که الگوریتمها به اطلاعات کلاس زمینه نیاز دارند، برای آن یک رابط تعریف کنید و کلاس زمینه را در آن قالب به الگوریتمها بدهید.
کاربران کلاس زمینه بر حسب نیازشان یکی از Strategyها را انتخاب و تنظیم کنند.
خوبیها:
الگوریتمهای داخل یک شئ را در زمان اجرا میتواند تغییر داد.
جزئیات پیادهسازی الگوریتمها را میتوان از پیادهسازی کلاسهایی که از آن استفاده میکنند جدا میکند.
به جای استفاده از وراثت از ترکیب اشیاء استفاده میکند.
چون میتوان بدون تغییر دادن کلاس زمینه الگوریتم اضافه و کم کرد، اصل Open/Closed را رعایت کرده است.
بدیها:
اگر تعداد کمی الگوریتم داشته باشیم که دیر به دیر عوض میشوند، لزومی ندارد که با اضافه کردن کلاسهای جدید و رابطی برای آنها پیادهسازی را پیچیده کرد.
کاربران (نه کلاسهای زمینه) باید از انواع الگوریتمها و Strategyها اطلاع داشته باشند تا بتوانند از آنها استفاده کنند.
بسیاری از زبانهای برنامهنویسی امروزی از انواع تابعها و برنامهنویسی Functional پشتیبانی میکنند. در نتیجه، در آنها میتوان بدون تعریف کردن کلاس و رابط همان کارکرد الگوی Strategy را ایجاد کرد.
ارتباط با سایر الگوها:
الگوهای Bridge و State: ساختاری مشابه با الگوی Strategy دارند، چرا که همگی از ترکیب اشیاء استفاده میکنند و وظایف را به کلاسهای دیگر محول میکنند، اما مسائل مختلفی را حل میکنند. این موضوع به این دلیل رخ میدهد که الگوها علاوه بر ساختار دادن به کد وظیفهی گسترش دایرهی لغات تیم را دارند و میتوانند مفهوم مسئله را بهتر انتقال دهند.
الگوی Command: ممکن است به نظر برسد این الگو همان الگوی Command باشد، چون که هر دو اجازه میدهند یک شئ را برای نمایش یک عمل در نظر بگیریم، اما قصدی متفاوت از آن دارد. درواقع با الگوی Command میتوان هر دستوری را به صورت یک شئ نمایش داد و آن را ذخیره کرد،بعداً اجرا کرد و یا به سرویسهای دیگر ارسال کرد. این در حالی است که الگوی Strategy صرفاً روشهای مختلف از انجام یک کار را نمایش میدهد.
الگوی Decorator: پوستهی رفتاری یک کلاس را عوض میکند، در حالی که الگوی Strategy ذات آن را تغییر میدهد.
الگوی Template Method: از وراثت استفاده و در نتیجه راه حلی ایستا است برای تغییر الگوریتم است، برخلاف Strategy که از ترکیب اشیاء استفاده میکند و راه حلی پویاست.
الگوی State: میتواند تعمیمی از Strategy تلقی شود، چرا که همان ساختار را دارد، با این تفاوت که اشیاء یاریدهنده به هم و نیز به شئ زمینه دسترسی داشته باشند و بتوانند یکدیگر را تغییر دهند، در حالی که در الگوی Strategy از این نظر استقلال فرض میشود.
الگوی Memento
هدف: هدف اصلی الگوی Memento، ذخیره و بازیابی وضعیت یک شیء در زمان مختلف است. با استفاده از این الگو، میتوان وضعیت یک شیء را در یک نقطه زمانی ذخیره کرده و در آینده به آن بازگشت کرد.
مسئله:
مسئله اصلی که الگوی Memento برای آن طراحی شده است، نیاز به ذخیره و بازیابی وضعیت یک شیء در زمان مختلف است. ممکن است در برنامههایی که با تغییرات پویا روبرو هستند، نیاز به ذخیره و بازیابی وضعیت شیء در زمانهای مختلف وجود داشته باشد.
راه حل: در بخش "Solution" لینک مذکور درباره الگوی Memento، به طور خلاصه موارد زیر آورده شده است:
الگوی Memento برای ذخیره و بازیابی وضعیت یک شیء در زمانی مشخص طراحی شده است. این الگو امکان ثبت وضعیت داخلی یک شیء را فراهم میکند و در زمان نیاز میتواند آن را بازیابی کند.
روش حل ارائه شده در این الگو این است که یک شیء با نام Memento ساخته میشود که وضعیت داخلی شیء اصلی را ذخیره میکند. سپس شیء اصلی میتواند با استفاده از این Memento، وضعیت خود را بازیابی کند.
با استفاده از الگوی Memento، میتوانیم وضعیت یک شیء را در زمان اجرا ذخیره کرده و در زمان نیاز به آن بازیابی کنیم. این الگو به برنامهنویسان اجازه میدهد تا تاریخچهای از وضعیتهای گذشته یک شیء را نگهداری کنند و به راحتی بین آنها جابجا شوند.
مزیت این الگو این است که جدا بودن فرآیند ذخیره و بازیابی وضعیت شیء از شیء اصلی، مسئولیتها را جدا میکند و اصلی بودن شیء را حفظ میکند. همچنین این الگو امکان بازیابی وضعیت شیء در زمانهای مختلف را فراهم میکند و قابلیت استفاده مجدد و تست آسان را به ما میدهد.
کاربردها:
ذخیره و بازیابی وضعیت بازیها: در بازیهای کامپیوتری، ممکن است نیاز به ذخیره و بازیابی وضعیت بازی در زمانهای مختلف وجود داشته باشد. الگوی Memento میتواند در اینجا مورد استفاده قرار گیرد.
بازگشت به وضعیت قبلی: در برنامههایی که تغییرات پویا دارند، ممکن است نیاز به بازگشت به وضعیت قبلی وجود داشته باشد. الگوی Memento میتواند در اینجا مورد استفاده قرار گیرد.
ذخیره و بازیابی وضعیت فرمها: در برنامههای وب، ممکن است نیاز به ذخیره و بازیابی وضعیت فرمها در زمانهای مختلف وجود داشته باشد. الگوی Memento میتواند در اینجا مورد استفاده قرار گیرد.
چگونگی پیادهسازی:
برای پیادهسازی الگوی Memento، باید مراحل زیر را دنبال کرد:
ابتدا باید یک کلاس Memento ایجاد کرده و در آن وضعیت شیء اصلی را ذخیره کنید. سپس باید یک کلاس Caretaker ایجاد کنید که Memento را دریافت کرده و در آینده بتواند وضعیت شیء اصلی را بازیابی کند. در نهایت، باید متدهای لازم برای ذخیره و بازیابی وضعیت در کلاس اصلی تعریف شوند.
خوبیها:
افزایش قابلیت توسعه و نگهداری: با استفاده از الگوی Memento، میتوان وضعیت یک شیء را در زمانهای مختلف ذخیره کرده و در آینده به آن بازگشت کرد. این باعث افزایش قابلیت توسعه و نگهداری برنامه میشود.
جدا بودن دادهها و عملیات: با استفاده از الگوی Memento، دادهها و عملیات به صورت جداگانه قرار میگیرند. این باعث افزایش قابلیت تست و تعمیر برنامه میشود.
بدیها:
استفاده از حافظه: استفاده از الگوی Memento ممکن است به مصرف بیشتر حافظه منجر شود، زیرا وضعیت شیء در زمانهای مختلف ذخیره میشود.
پیچیدگی در پیادهسازی: پیادهسازی الگوی Memento ممکن است پیچیدگی بالایی داشته باشد، زیرا باید کلاسهای Memento و Caretaker را ایجاد و متدهای لازم را تعریف کرد.
ارتباط با سایر الگوها:
الگوی Command: با الگوی Memento ارتباط دارد، زیرا ممکن است نیاز به ذخیره و بازیابی وضعیت یک عملیات در زمانهای مختلف وجود داشته باشد.
الگوی Observer: با الگوی Memento ارتباط دارد، زیرا ممکن است نیاز به ذخیره و بازیابی وضعیت یک شیء در زمانهای مختلف وجود داشته باشد.
الگوی Observer
قصد: این یک الگوی رفتاری است که مکانیزمی برای به اطلاع رساندن رخ یک رخداد را به اطلاع اشیا مختلفی که به آن رویداد حساس هستند را فراهم کند.
مسئله: در نظر بگیرید که یک رویداد، مانند عرضه یک محصول به بازار، برای تعداد زیادی از افراد ممکن است حائز اهمیت باشد. در حالت ساده، این افراد همگی آنها مستقلا باید هر از چندگاهی پیگیر عرضه و یا عدم عرضه آن محصول به بازار شوند. این مراجعههای متعدد تعداد زیادی مشتری که خواهان محصول هستند، زحمت زیادی چرا برای خودشان چه برای فروشنده پاسخگو به همراه دارد و حتی ممکن است باعث ترافیک در مسیر شود!
راه حل: راهکار سادهتری نیز وجود دارد: اینکه این افراد خود را به فروشگاه عرضه کننده محصول معرفی کنند و زمانی که محصول عرضه شد، این فروشگاه از طریق نامه و یا ایمیل به آنها اطلاع رسانی کند. در این حالت دیگر نیازی به اینکه مسیر بین فروشگاه و منزل دائما طی شود وجود ندارد و به محض اینکه محصول عرضه شد، عرضه محصول به اطلاع همگان خواهد رسید. برای این منظور باید متد واسطی به عنوان نحوه اطلاع رسانی معرفی شود که در صورت رخ دادن آن رویداد، آن متد با ورودی مقتضی فراخوانی شود.
کاربردها:
زمانی که تغییر یک مقدار یا رویدادن یک رخداد نیازمند اقدام از طرف گروهی از کلاسها باشد که در زمان اجرا مشخص میشوند. در این حالت در زمان اجرا بسته به شرایط هرکسی میتواند اشتراک اطلاع از رویداد را دریافت کند.
زمانی که در مدت محدودی، و نه همیشه رخ دادن یک رویداد برایمان اهمیت داشته باشد. در این صورت بعد از گذشت زمان میتوانیم اشتراک را حذف نماییم.
چگونگی پیادهسازی: برای پیادهسازی این الگو نیاز است که که دو واسط طراحی کنیم. ۱) واسطی برای طرف اشتراکگذارنده و ۲) واسطی برای طرف اشتراکگیرنده. واسط طرف اشتراکگذارنده باید متدهایی را برای دریافت اشتراک و حذف اشتراک و همچنین اطلاع رسانی به بقیه پیادهسازی کند و واسط طرف گیرنده باید متد دریافت update را داشته باشد. هر کلاسی با پیادهسازی این این واسطها میتواند در این نقش قرار بگیرد.
خوبیها:
این الگو به خوبی قاعده Open/Closed را رعایت میکند. با ارائه واسط برای هر یک از طرفین به خوبی میتوان این الگو را رعایت کرد.
ارتباط بین اشیا را میتوان در زمان اجرا مشخص کرد.
بدیها:
ترتیب فراخوانی مشترکین رویداد تصادفی خواهد بود و نمیتوان ترتیب خاصی را برای این منظور به طور پویا اعمال کرد.
ارتباط با سایر الگوها:
سه الگوی Chain of Responsibility و Command و Mediator: برای مدیریت اشتراکگذاری رویداد، این سه الگو نیز وجود دارند. رنجیره مسئولیت برای برقراری ترتیب و فرمان برای براقراری رابطه دوطرفه راهکار مناسبی است. میانجی نیز الگویی است که ارتباطات مستقیم را حذف و آنرا به یک کلاس هدایت میکند.
الگوی Mediator: گاها میتوان این الگو و نظارهگر را همزمان با هم پیادهسازی کرد.
جمعبندی و پیشنهادات
در این نوشته، خلاصهای برای آشنایی با الگوها، به طور خاص الگوهای طراحی Gang of Four، ارائه شد. به عبارتی، با ارائه دادن انگیزه برای استفاده از این الگوها و روش مناسب انتخاب یک الگو، گامهای اولی که یک طراح باید در راستای انجام یک طراحی خوب بردارد تشریح شد. پیشنهاد میشود که خوانندهی علاقهمند برای بهرهمندی بیشتر از این الگوها، نگاهی به مراجع و به طور خاص خود کتاب Design Patterns: Elements of Reusable Object-Oriented Software بیندازد تا سایر الگوهای Gang of Four را نیز بیاموزد. همچنین، چون که مفهوم الگو یک مفهوم کلی است، پیشنهاد میشود که یک خواننده به دنبال بررسی الگوها در حطیهی خاص کاری خود بگردد. مثالهایی از این حیطهها میتواند تحلیل نیازمندیها، معماری نرمافزار، طراحی میکروسرویسها، طراحی رابط کاربری، برنامهنویسی رویهای (Procedural Programming)، برنامهنویسی تابعی (Functional Programming) و طراحی همروندی و توزیعشدگی باشد، چرا که اینها مسائلیاند که یک مهندس نرمافزار در دنیای صنعت با آنها رو به رو خواهد شد و الگوهای Gang of Four آنها را شامل نمیشود. در نتیجه، خوب است که افراد با الگوهایی که مربوط به حوزهی فعالیتشان است بیشتر آشنا شوند تا هم محصول بهتری تولید کنند و هم مهندسان بهتری شوند.
مراجع و منابع
[1] Gamma, E., Helm, R., Johnson, R. and Vlissides, J. (1994). Design patterns : elements of reusable object-oriented software. Boston: Addison-Wesley.
[2] Lieberman, H. (1986). Using prototypical objects to implement shared behavior in object-oriented systems. Conference Proceedings on Object-Oriented Programming Systems, Languages and Applications, 21(11), pp.214–223. doi:https://doi.org/10.1145/960112.28718.
[3] Refactoring.guru. (2014). Design Patterns. [online] Available at: https://refactoring.guru/design-patterns.
[4] Pressman, R. (2014). Software engineering. New York: Mcgraw-Hill.