2.5. Decorator¶
2.5.1. تعریف¶
دیزاین پترن Decorator یک الگوی ساختاری محسوب میشه که به ما اجازه میده بدون تغییر یک آبجکت بتونیم بهش عملکرد جدید اضافه کنیم و یک جایگزین برای استراتژی ایجاد زیر کلاس برای گسترش عملکرد محسوب میشه.
در واقع یک کلاس Decorator خواهیم داشت که کلاس اصلی رو در بر میگیره (wrapping) و عملکرد های جدیدی رو بهش اضافه می کنه.
نکته مهم اینجا اینه که کلاس Decorator و کلاس پایه ی ما هر دو یک interface رو پیاده سازی می کنن و همین باعث میشه که امکان جایگزینیشون فراهم بشه.
کلاس Decorator همچنین یک ارجاع به کلاس اصلی رو هم در خودش نگهداری می کنه.
2.5.2. اجزاء¶
برای مشخص کردن اجزاء مختلف باید ابتدا مطمئن بشید که برنامه شما میتونه در قالب یک Main Component با چند لایه (اختیاری) که اون رو wrap کرده در بیاد.
بعد بررسی می کنیم که چه متدهایی بین کامپوننت اصلی و لایه های اختیاری مشترک هستن که بتونیم Component Interface رو تشکیل بدیم.
حالا یک Concrete Component ایجاد می کنیم و behavior پایه اون رو مشخص می کنیم.
حالا کلاس Base Decorator رو ایجاد می کنیم که شامل یک فیلد برای نگهداری ارجاع به آبجکت Wrapped هست. نوع این فیلد هم باید از نوع interface ای که ابتدا تعریف کردیم یعنی Component Interface باشه.
و حالا Concrete Decoration ها رو با extend کردن از Base Decorator ایجاد می کنیم.
و بعد هم که طبق معمول Client Code وارد میدان میشه و با استفاده از مواردی که نیاز برنامه هست از این ترکیب استفاده می کنه.
Vanderjoe, CC BY-SA 4.0, via Wikimedia Commons
2.5.3. چه زمانی استفاده میشه؟¶
این الگو رو زمانی استفاده می کنیم که مطمئن بشیم میشه برنامه رو در قالب یک بخش اصلی و یک سری لایه های اختیاری که اون رو wrap می کنن تعریف کنیم.
Caution
✅ مزایای استفاده
امکان افزودن عملکرد و قابلیت های جدید به یک آبجکت موجود به صورت داینامیک بدون تغییر ساختار اصلی و افزودن زیرکلاس
رعایت اصول Open/Closed و تک مسئولیتی به علت عدم تغییر ماهیت و ساختار کد در زمان افزودن امکانات و قابلیت های جدید و جدا نگهداشتن کلاس اصلی و قابلیت های افزوده شده در کلاس های Decorator مجزای جدید و در نتیجه افزایش قابلیت نگهداری برنامه
امکان ترکیب چندین عملکرد و رفتار با wrap کردن یک آبجکت در چندین Decorator مختلف
Warning
❌ معایب استفاده
پیچیدگی های موجود در برای حذف و افزودن wrapper ها و همچنین پیچیده به نظر رسیدن کد
2.5.4. کاربرد عملی¶
خب تصور کنید که یک سیستم ارسال ناتیفیکیشن پایه داریم که قصد داریم به شکل های مختلف اون رو گسترش بدیم، مثلا ارسال توسط روش های مختلف ایمیل و SMS و…
اینجاست که یاد دیزاین پترن Decorator میفتیم.
2.5.5. پیاده سازی¶
قبل از هر چیز یک interface مشترک داریم که کارش ارسال ناتیفیکیشن هست.
1<?php
2
3/**
4 * The base Notification interface
5 */
6interface Notification
7{
8 public function send(): string;
9}
کلاس پایه ای که قصد گسترشش توسط دیزاین پترن Decorator رو داریم به این شکل تعریف شده و به شکل ساده ای پیام رو ارسال می کنه.
1<?php
2
3/**
4 * Concrete Notifications
5 */
6class ConcreteNotification implements Notification
7{
8 public function send(): string
9 {
10 return "Sending a concrete notification";
11 }
12}
بعد از این ما یک کلاس پایه ی Decorator داریم که همون کلاس اصلی Notification رو پیاده سازی می کنه و هدف اصلیش مشخص کردن یک الگوی مشخص برای Decorator هایی هست که از این به بعد برای کلاس پایه تعریف میشن. و معمولا شامل یک ارجاع به کلاس پایه یعنی ConcreteNotification هست.
1<?php
2
3/**
4 * The base Decorator class
5 */
6class NotificationDecorator implements Notification
7{
8 /**
9 * @var Notification
10 */
11 protected Notification $notification;
12
13 public function __construct(Notification $notification)
14 {
15 $this->notification = $notification;
16 }
17
18 /**
19 * @return string
20 */
21 public function send(): string
22 {
23 return $this->notification->send();
24 }
25}
بعد از این کلاس های Concrete مربوط به Decorator رو داریم که روش های مختلف Decorate شدن کلاس پایه رو مشخص می کنن.
مثلا ارسال با SMS و ارسال با Email
1<?php
2
3/**
4 * Concrete Decorators
5 */
6class SMSNotificationDecorator extends NotificationDecorator
7{
8 public function send(): string
9 {
10 return "Sending an SMS notification. " . parent::send();
11 }
12}
13
14class EmailNotificationDecorator extends NotificationDecorator
15{
16 public function send(): string
17 {
18 return "Sending an email notification. " . parent::send();
19 }
20}
توجه کنید که Decorator ها میتونن پیاده سازی Parent رو به این شکل در متد تغییر یافته فراخوانی کنن.
این فراخوانی می تونه قبل یا بعد از فراخوانی آبجکت wrap شده باشه.
2.5.6. نحوه فراخوانی¶
1<?php
2
3function clientCode(Notification $notification): void
4{
5 echo $notification->send();
6}
7
8/**
9 * This way the client code can support both simple notifications...
10 */
11$simple = new ConcreteNotification();
12echo "Client: I've got a simple notification:\n";
13clientCode($simple);
14echo "\n\n";
15
16/**
17 * ...as well as decorated ones.
18 *
19 * Note how decorators can wrap not only simple notifications but the other
20 * decorators as well.
21 */
22$smsNotification = new SMSNotificationDecorator($simple);
23$emailNotification = new EmailNotificationDecorator($smsNotification);
24echo "Client: Now I've got a decorated notification:\n";
25clientCode($emailNotification);
26
27// Output:
28// Client: I've got a simple notification:
29// RESULT: ConcreteNotification
30//
31// Client: Now I've got a decorated notification:
32// RESULT: EmailNotificationDecorator(SMSNotificationDecorator(ConcreteNotification))
همونطور که میبینید متد clientCode اینجا هم میتونه از نوع پایه ورودی بگیره هم از نوع Decorator و هم از ترکیبی از Decorator ها