دیزاین‌پترن Decorator به زبان ساده

امروز به خاطر یه قسمتی از کارم، سر و کارم به دیزاین‌پترن Decorator افتاد و به این فکر افتادم چیزایی که استفاده کردم و قبلاً یادگرفتم رو بنویسم تا هم برای خودم یادآوری بشه و مهم‌تر از همه به اشتراک‌گذاری دانش کار خوبیه 🙂، شاید یک نفر با جستجو به این موضوع رسید و گره‌ای از کارش باز شد؛ قصدم آموزش دادن نیست، بیشتر قصدم اینه دور همدیگه در موردش حرف بزنیم. در انتهای مطلب منابع آموزش خوب رو هم بهتون معرفی می‌کنم که اگر خواستید بیشتر پیگیرش بشید.

 اضافه کردن قابلیت‌های جدید در دیزاین‌پترن Decorator
اضافه کردن قابلیت‌های جدید در دیزاین‌پترن Decorator
نکته‌ی کوتاه: اگر فراموش کردید دیزاین‌پترن‌ها چی هستند پست قدیمی من [+] رو بخونید؛ اونجا یک توضیح کوتاه دادم؛ ولی اگر هیچی از دیزاین‌پترن‌ها نمی‌دونید، فکر نمی‌کنم این مطلب مناسب‌تون باشه چون پایه‌ی کار رو نمی‌خوام توضیح بدم.

دیزاین‌پترن Decorator چیست؟

دیزاین‌پترن Decorator (که به نام Wrapper هم شناخته می‌شه) یک دیزاین‌پترن از مجموعه ۲۳ دیزاین‌پترن منسوب به Gang of Four است و در دسته‌بندی دیزاین‌پترن‌های «ساختاری» (به انگلیسی: structural) قرار می‌گیره. دیزاین‌پترن‌های ساختاری مربوط به نحوه تشکیل و ساختار کلاس‌های ما هستند و درواقع کمک می‌کنند اون بدنه و منطق کلاس‌های ما ارتباط بهتری باهم داشته باشند، بتونیم ساختارهای بزرگتر و پیچیده‌تر تولید کنیم و در کنار این قابلیت تست و توسعه‌پذیری رو هم حفظ کنیم.

به زبان خیلی ساده Decorator به ما کمک می‌کنه بدون تغییر دادن ساختار کلاس‌مون، بهش قابلیت جدید اضافه کنیم، می‌تونید Decorator رو یک لایه جدید دور کلاس‌تون ببینید.

چرا و کِی باید از دیزاین‌پترن Decorator استفاده کنیم؟

سوال خیلی خوبی پرسیدید، حالا که می‌دونیم Decorator چی هست و در چه دسته‌ای قرار می‌گیره، بهتره ببینیم چرا باید ازش استفاده کنیم. برای اینکه به این سوال پاسخ بدیم باید اول از خودمون یک سوال دیگه بپرسیم؛ «ما در برنامه‌مون چه مشکلی داریم؟/چه مشکلی با این دیزاین‌پترن حل می‌شه؟»

بسیار عالی؛ سوال خوب نیاز به جواب خوب داره، ما زمانی از دیزاین‌پترن Decorator استفاده می‌کنیم که به چالش/پیچیدگی زیر برخورده باشیم:

  • زمانی که می‌خواهیم یک مسئولیت/قابلیت به یک کلاس اضافه کنیم بدون اینکه این قابلیت اضافه به تمام نمونه (instance)ـهای اون کلاس دخالتی ایجاد کنه.

به بیان دیگه، Decorator یک راه حل جایگزین برای اضافه کردن قابلیت‌های انطعاف‌پذیر به جای ساختن زیرکلاس هست.

بسیار خب، بذارید یک مثال براتون بزنم تا این کلمات کنار هم شما رو سردرگم نکنه؛ تصور کنید درحال نوشتن یک مجموعه ابزار UI (رابط کاربری) برای یک نرم‌افزار هستید، مدیر پروژه به شما گفته هر کامپوننت (قطعه یا بخش نرم‌افزاری) باید بتونه در شرایط لازم برای کاربر قابل Scroll باشه و همچنین یه border یا قاب رنگی خوشگل هم اطرافش رسم بشه. بیایید موضوع رو به قسمت‌های کوچک‌تر بشکنیم، یعنی ما داخل برنامه‌مون یه سری کامپوننت داریم که با وجود کارایی‌ها و پیاده‌سازی‌های متفاوت ممکنه یک سری عملکرد (functionality) خاص داشته باشند و این عملکردها زمان اجرا به برنامه اعمال بشوند. چطور می‌تونیم به این مهم دست پیدا کنیم؟

مثال بالا نمونه‌ی خوبی هست که بفهمیم کی باید از دیزاین‌پترن Decorator استفاده کنیم. طبق تعریف اصلی، زمانی که می‌خواهیم یک مسئولیت (مثلاً در اینجا اضافه شدن border) به یک کلاس (مثلاً در اینجا یک component در رابط کاربری) اضافه کنیم بدون اینکه این قابلیت اضافه به نمونه‌های دیگه (کامپوننت‌های دیگه) دخالتی ایجاد کنه.

هر قابلیت (border و scroll) مثل یک لایه جدا به کلاس شما اضافه می‌شوند – منبع عکس: کتاب Gang of Four [+]
هر قابلیت (border و scroll) مثل یک لایه جدا به کلاس شما اضافه می‌شوند – منبع عکس: کتاب Gang of Four [+]

فرق Decorator با ارث‌بری چیست؟

زمانی که خوب به این موضوع دقت کنید می‌بینید به راحتی می‌شه مسئله‌ای که داریم رو با ارث‌بری هم حلش کنیم، یعنی به ارث بردن از کلاس ‌Border برای هر کامپوننت، این قابلیت رو بهمون می‌ده که کامپوننت‌هامون یک قاب مثلاً قرمز رنگ دور خودشون داشته باشند، اما زمانی که این کار رو بکنیم دیگه انعطاف‌پذیری رو در معماری نرم‌افزارمون از دست می‌دیم و موقع اجرا هم دسترسی به تغییرات نداریم.

این رو هم بهتره یادتون باشه که ارث‌بری در جای خودش قابلیت خوبیه اما جایی که می‌شه ازش استفاده نکرد، بهتره که اصلاً استفاده نشه، من دو مورد از نکات منفی ارث‌بری رو اینجا بهتون می‌گم:

  • ارث‌بری ایستا هست، یعنی موقع اجرا نمی‌تونید رفتار آبجکت/شی ایجاد شده رو تغییر بدید.
  • در بعضی از زبان‌های برنامه‌نویسی زیرکلاس‌مون فقط می‌تونه از یک کلاس ارث‌بری کنه و این خودش محدودیت و مشکل‌هایی برای معماری نرم‌افزارمون به وجود میاره که نتیجه‌اش می‌تونه شلوغ شدن بیش از حد باشه.

راه حل دیگه‌ای که ممکنه به ذهنتون برسه اینه که داخل هر کامپوننت بیایم یک متد متناسب با قابلیت‌مون اضافه کنیم که هرموقع خواستیم صداش کنیم و کامپوننت‌مون یک قاب دورش بیاد یا قابلیت Scroll بهش اضافه بشه، راستش این هم راه حل خوبی نیست چون:

  • ۱) مدام خودمون رو تکرار می‌کنیم و این اصلاً خوب نیست، خصوصاً برای تغییرات آینده
  • ۲) خیلی از اصول SOLID رو داریم نقض می‌کنیم خصوصاً اصل Open-Closed [+] و این تاثیر مستقیم روی مقیاس‌پذیری (Scalability) نرم‌افزارمون داره.

کُد و مثال:

بیایید یک مثال جدید رو خیلی ساده پیاده‌سازی کنیم تا در جمع‌بندی تمام این صحبت‌ها بهتون کمک کنه 🙂

تصور کنید یک برنامه در مورد قهوه دارید که در اون برنامه هر قهوه یک توضیح و قیمت متفاوت داره. در این مورد می‌تونیم از دیزاین‌پترن Decorator استفاده کنیم.

ابتدا یک interface تعریف می‌کنیم تا رابط کلی معماری‌مون رو مشخص کنه (مثال‌ها به زبان PHP نوشته شده‌اند اما تغییری در اصل ماجرا ایجاد نمی‌کنه و دیزاین‌پترن‌ها وابسته به زبان یا فریم‌ورک خاصی نیستند):

بعد سه کلاس از نوع «قهوه ساده»، «قهوه با شیر» و «قهوه وانیلی» می‌سازیم که هرکدوم قیمت و توضیحات متفاوتی دارند و همه‌شون خصوصیات اینترنفیس Coffee رو پیاده‌سازی می‌کنند.

بسیار عالی، در ادامه هم، خروجی برنامه رو تست می‌کنیم تا از صحت عملکردش مطمعن بشیم.

امیدوارم این مطلب براتون مفید واقع شده باشه، پستی که خوندید ترجمه شده از یه بخش یا منبع مشخص نیست، بلکه درک، برداشت و تجربه‌ی شخصیم هست. برای اطلاعات بیشتر می‌تونید به این منابع مراجعه کنید.

منابع:

  • کتاب Design Patterns: Elements of Reusable Object-Oriented Software معرف به Gang of Four [لینک +]
  • مقاله Decorator از وبسایت refactoring [لینک +]
  • آموزش سایت tutorialspoint [لینک +]
  • مجموعه کدهای کتاب Gang of Four به زبان PHP در گیت‌هاب [لینک +]
  • ویکی‌پدیا [لینک +]