الگوی طراحی دکوراتور (Decorator) در زبان PHP

قبل از پرداختن به توضیح مطالب این پست ٬ به توضیحاتی ابتدایی در مورد الگویهای طراحی (Design Patterns) میپردازیم.
الگوی طراحی چیست؟
الگوهای طراحی٬ راه حل هایی ثابتشده برای حل مشکلات رایج در برنامه نویسی شئ گرا هستند. توضیحات ویکی پدیا:
در مهندسی نرمافزار، الگوی طراحی (Design Pattern) یک راهحل عمومی قابل تکرار برای مشکلات متداول در زمینه طراحی نرمافزار است. الگوی طراحی، یک طراحی تمامشده نیست که به صورت مستقیم بتواند تبدیل به کد منبع یا ماشین شود؛ بلکه، یک توضیح یا قالب برای حل یک مسئله در شرایط مختلف است. الگوها در واقع بهترین روش ممکن هستند که یک برنامهنویس میتواند در هنگام طراحی یک برنامه برای حل مشکلاتش از آنها استفاده کند.
الگوهای طراحی به ۳ دسته کلی تقسیم میشوند:
- الگوهای طراحی تکوینی یا ایجادی (Creational) برای ساخت کلاس ها و شئ ها
- الگوهای طراحی ساختاری (Structural) برای ترکیب کلاس ها و شئ ها
- الگوهای طراحی رفتاری (Behavioral) برای روابط بین شئ ها
الگوی طراحی دکوراتور یکی از الگوهای طراحی ساختاری هست و امکان اضافه کردن عملکرد به اشیا را در زمان اجرا فراهم می کند.
توضیحات ویکی پدیا:
در برنامهنویسی شئ گرا، الگوی آذینگر یا دکوراتور (decorator) یک الگوی طراحی است که امکان افزودن رفتار (behavior) به یک شئ، را بهطور پویا (dynamic) یا ایستا (static) فراهم میسازد بی آنکه رفتار اشیاء دیگر از همان کلاس (که شئ مورد بحث از آن ساخته شده) دستخوش تغییر شوند. الگوی طراحی آذینگر معمولاً برای پایبندی به قاعدهٔ تک وظیفهای مورد استفاده قرار میگیرد چرا که این الگوی طراحی، امکان تقسیم عملکردها بین کلاسهای مختلف که هر کدام دغدغههای خاص را پوشش میدهند، فراهم میسازد.
حال برای مثال ٬ یک سناریوی فرضی را در نظر میگیریم:
ما یک شرکت خدمات وب داریم و میخواهیم یه سری خدمات رو به مشتریان ارائه بدیم, پس مسلما نیاز داریم تا قیمت این خدمات رو حساب کنیم. یکی از ابتداییترین سرویسها برای چنین شرکتی٬ میزبانی وب هست. اگر بخواهیم سادهترین حالت کد برای این کار رو در نظر بگیریم.. چیزی شبیه زیر داریم:
حالا حالتی رو در نظر بگیرید که میخواهیم قیمت میزبانی وب + ثبت دامنه رو حساب کنیم. شاید اولین راهی که به ذهن شما برسه ایجاد یک کلاس برای دریافت قیمت هر دو سرویس باشه. شبیه زیر:
ممکنه فکر کنید که خیلی خوب ٬ کدتون کار میکنه و این جور کد نوشتن هیچ مشکلی نداره... اما اگر کمی به تمام حالات ممکن که میشه این سرویسها رو با هم ترکیب کرد فکر کنیم٬ متوجه میشیم که حالات زیادی وجود دارند.
برای مثال حالتی رو در نظر بگیرید که در کنار ۲ سرویس قبلی٬ یه سرویس مدیریت نیم سرورها هم به موارد اضافه میشه:
همانطور که میبینید ٬ این سبک کد نوشتن به سادگی ما رو دچار مشکل میکنه. به چند علت:
- برای یک شرکت خدمات وب٬ تعداد کلاسهای ترکیبی که باید داشته باشیم خیلی زیاد میشه.
- ما قیمت سرویسها رو اصطلاحا Hard-Code کردیم٬ پس اگر مبلغ هر کدوم از خدمات ما عوض بشه باید در چندجا اونها رو تغییر بدیم.
- در این حالت راهی برای گسترش عملکرد و توسعه بیشتر کدها وجود نداره.
خوب نمیشد اگر میتوانستیم این اشیا رو در حین زمان اجرا و بهطور مستقل بسازیم؟ و یک دکوراتور دقیقا همین قابلیت رو به ما میده...
طریقه پیادهسازی این سیستم با الگوی طراحی دکوراتور:
- یک اینترفیس (Interface) بوجود میاریم.
- در تمام کلاسها الزام تبعیت از قرارداد (اینترفیس) رو بوجود میاریم.
- تمام کلاسها رو٬ بغیر از کلاس اولیه (در اینجا WebHosting) به دکوراتور تبدیل میکنیم.
و برای تبدیل کلاسها به دکوراتور:
- از طریق متد سازنده (construct__) کلاس٬ یک وابستگی که از اینترفیس تبعیت کنه٬ تزریق میکنیم.
- متد getCost رو آپدیت میکنیم تا قیمت هر سرویس رو با وابستگی تزریق شده جمع کنه.
پس کدی که باید داشته باشیم٬ به این شکله:
و در نهایت کلاس اولیه رو در کلاس دکوراتور میپیچیم و بصورت زیر استفاده میکنیم:
به این حالت ما میتونیم٬ این اشیا رو در حین زمان اجرا و کاملا پویا داشته باشیم.
نتیجه گیری
با توضیحاتی که داده شد٬ ممکنه این سوال پیش بیاد که چه هنگام باید از این الگو و چه هنگام باید از وراثت استفاده کرد؟
به عنوان یه قاعده کلی مراقب استفاده از وراثت باشید. وراثت حتما جایگاه خودشو داره اما مراقب باشید که تمام عملکردهایی که ممکنه حتی بهشون نیاز هم نداشته باشید رو به کلاستون منتقل نکنید. در حالی که میتونید از الگوی کامپوزیت یا دکوراتور استفاده کنید.