توسعه نرم افزار رادکام

اخبار، مطالب و رویدادهای مرتبط با توسعه نرم افزار رادکام

الگوی طراحی Decorator

الگوی طراحی Decorator در رده الگوهای طراحی Structural قرار دارد. رسالت الگوی Decorator این است که راهی برای اتصال و تخصیص یک وضعیت یا رفتار جدید به یک شیء به صورت پویا فراهم کند. خود شیء در جریان این اتصال نیست، یعنی خود شیء نمی داند که رفتار و یا وضعیت جدیدی به آن متصل و اضافه شده است، به همین دلیل این الگو در سامانه های در حال توسعه کاربرد بنیادین دارد. یک نقطه کلیدی پیاده سازی در الگوی Decorator این است که دکوراتورها هر دو کلاس اصلی را به ارث می برند و  همچنین شامل نمونه ای از آن هستند.
همانطور که از نام الگو پیداست، الگوی Decorator یک شیء موجود را گرفته و رفتار یا وضعیت و یا یک موجودیتی را به آن اضافه می کند. به عنوان مثال تصویر زیر را در نظر بگیرید. روش های متعددی برای اضافه کردن یک موجودیت یا صفت جدید به یک تصویر وجود دارد، به عنوان نمونه، اضافه کردن حاشیه به اطراف آن، و یا اضافه کردن تگ مرتبط با متن تصویر به آن. چنین مواردی که به تصویر اضافه می شوند، می توانند در بالا با پایین آن نمایش داده شوند.


DecoratorPattern-01-PlainPhotograph  DecoratorPattern-02-PhotographWithTags


ترکیبی از شیء اصلی ( در اینجا تصویر اصلی ) و برخی از مطالب و صفات جدید ، یک شیء جدید ایجاد می کند. در تصویر دوم  (تصویر سمت چپ) چهار شیء جدید وجود دارد که در تصویر اصلی فقط یکی از آنهاست، در قیاس با تصویر اصلی در سمت راست، شیء ای وجود دارد که حاشیه را ایجاد کرده است، و دو شیء با نام تگ که محتوای متفاوتی دارند. هر کدام از این اشیاء یک Decorator محسوب می شوند. با توجه به این نکته که تعداد روش های تزئین (Decoration) عکس ها بی حد و حصر است، می توانیم انواع گوناگونی از اشیاء جدید را داشته باشیم.
زیبایی این الگو در این است که:

  • شیء اصلی از وجود هر کلاس دکوری (Decorator) بی اطلاع است.
  • هیچ کلاس بزرگی نداریم که تمام ویژگی ها بزرگ و تمام گزینه های موجود را داشته باشد.
  • همه Decorator ها مستقل از یکدیگر هستند.
  • Decorator ها می توانند با همدیگر به روش های مختلفی ترکیب شوند.
اکنون ما می توانیم نقش آفرینان در الگوی Decorator را در نمودار UML زیر مشخص کنیم.

Decorator Pattern UML Diagram

نقش آفرینان اصلی در دیاگرام UML فوق عبارتند از:
  • Component
    کلاس اصلی از اشیاء که یک سری عملیت می توانند به آن اضافه شده و یا در آن بروزرسانی شوند. (بیش از یک کلاس Component  هم می تواند وجود داشته باشد)
  •  Operation
    یک یا چند عملیات در اشیاء از نوع IComponent که می توانند جایگزین شوند.
  • IComponent
    رابطی که کلاس هایی از اشیا را مورد هدف قرار می دهد (شناسایی می کند) که قرار هست توسط Decorator مورد تغییر (تزئین) قرار گیرند.
    (کلاس Component یکی از این کلاس هایی است که مورد هدف Decorator هاست)
  • Decorator
    کلاس یا کلاس هایی هستند منطبق و مطابق با رابط IComponent (از آن ارث بری می کنند)، که حالات و رفتار های جدید تری را به آن اضافه می کنند.
در مرکز دیاگرام UML کلاس Decorator قرار دارد. این کلاس شامل دو نوع رابطه با اینترفیس IComponent است.

رابطه Is-a
این یک رابطه ای است که با فلش خط چین شده از Decorator به IComponent نمایش داده شده است. و نشان می دهد که کلاس Decorator  ، اینترفیس IComponent را پیاده سازی می کند.  این که کلاس Decorator از اینترفیس IComponent ارث بری می کند بدین معنی است که، اشیاء Decorator می توانند در جاهایی که نیاز به استفاده از کلاس IComponent هست، استفاده شوند. کلاس Component در یک رابطه پدر فرزندی (is-a) با اینترفیس IComponent است، بنابراین برنامه نویس می تواند اشیاء از نوع Component و Decorator را به طور متناوب استفاده کند. (مشخصه اصلی الگوی Decorator). (نکته: عبارت "به طور متناوب" به عنوان ترجمه عبارت interchangeably آورده شده است، شاید استفاده از عبارت "با هم عوض کردنی" بیشتر نشان دهنده معنی عبارت اصلی در این جمله باشد.)

رابطه Has-a
رابطه has-a رابطه ایست که با شکل یک لوزی (یا الماس) متصل به کلاس Decorator نشان داده شده است که توسط یک خط به کلاس IComponent متصل شده است. این بدین معنی است که، کلاس Decorator یک یا چند نمونه از اشیاء IComponent را ایجاد می کند ( ترجمه ای برای واژه Instantiate )، و در نتیجه اشیاء ایجاد شده بر اشیاء اصلی مقدم می شوند. Decorator از ویژگی IComponent بودن کلاس Component استفاده می کند تا هر عملیاتی را که می خواهد جای عملیات کلاس اصلی جایگزین کند. این روشی است که الگوی Decorator عمل می کند.
متد addState و ویژگی addedState در کلاس Decorator راه اختیاری دیگری است که می توانیم با استفاده از آن امکانات، ویژگی ها و متد های کلاس اصلی را گسترش دهیم.
به مثال زیر توجه کنید، در این مثال می خواهیم دو تصویر اول مقاله را با استفاده از الگوی Decorator شبیه سازی کنیم. جدول زیر را ببینید:

IComponent Any photo
Component A plain photo
Operation To display a photo
Decorator A tagged photo
Client Creator of photos and tagged photos
IComponent Any photo

با توجه به تصویر مربوطه  و جدول فوق که بر مبنای الگوی Decorator ایجاد شده است، کد زیر  عمل تخصیص (چسباندن) دو تگ را روی تصویر اصلی انجام می دهد:

Photo photo = new Photo( );
Tag foodTag = new Tag (photo, "Food",1);
Tag colorTag = new Tag (foodTag, "Yellow",2);

 بر اساس رابطه is-a ، تصویر (Photo )، تگ Food و تگ Color  هر سه جزو اشیا IComponent  هستند. هر کدام از تگ ها ( که Decorator هستند )،  با یک Component ایجاد می شوند، که این Component می تواند تصویر یا تگ باشد. دیگرام مربوطه به شکل زیر خواهد بود:

Food Uml Diagram

همانطور که در تصویر دیگرام می بینید، در واقع سه شیء مختلف Photo در ساختار ما وجود دارد. حال کمی بعد، در بخش پیاده سازی به چگونگی تعامل بین آنها خواهیم پرداخت،
در بسیاری از الگو ها ما با این حالت مواجه می شویم که، اشیاء ما می توانند در لباس های مبدل متفاوتی ظاهر شوند،  البته برای ساده و  شفاف بودن دیاگرام UML، همه موارد را در آن دیاگرام لحاظ نمی کنیم، با این حال ما باید مفاهیم مربوط به این چندگانگی را در طراحی الگو، در نظر بگیریم:

تعدد در وجود اشیاء از نوع Component:
Component های گوناگونی که  با اینترفیس ما مطابقت دارند، می تواند از طریق Decorator پیاده سازی شوند. برای مثال، ما می توانیم کلاسی داشته باشیم که اشکال زیادی مانند، افراد، خانه و کشتی  رسم کند، و همچنین می تواند اشکال و خطوط ساده را نیز طراحی کند. این دو دسته کلاس هم می توانند برچسب گذاری شوند(یعنی تگ  به آنها چسبانده شود). به همین دلیل است که می گوییم اینترفیس IComponent از اهمیت بالایی برخوردار است. حتی اگر هیچ عملیاتی هم داخل آن وجود نداشته باشد. در مواردی این چنین ، زمانی که ما اطمینان داریم که  تنها یک کلاس Comnponent وجود دارد، ما می توانیم اینترفیس IComponent  را  در نظر نگیریم و  Decorator ها را مستقیما از Component ارث بری کنیم.

تعدد در وجود اشیاء از نوع Decorator:
همانطور که مشاهده فرمودید، ما می توانیم نمونه های مختلفی از Decorator برچسب ایجاد کنیم. ما همچنین می توانیم انواع دیگری از Decorator ها را داشته باشیم، مانند Decorator ایجاد کننده حاشیه برای تصویر، یا Decorator ای که تصویر را غیر قابل مشاهده نماید. مهم نیست که دکوراتور ها چیستند، ولی هر کدام از Decorator ها شامل یک شیء از Component هستند که خود آن نیز میتواند یک Decorator باشد، که در این صورت می توان زنجیره ای از تغییرات را اعمال کرد. برخی ها در الگوی طراحی Decorator از اینترفیس IDecorator  استفاده می کنند، که خیلی در پیاده سازی تاثیر شاخصی ندارد.

تعدد در وجود عملیات (Operations):
هدف ما تمرکز بر روی عملیات اصلی و بنیادی بر روی تصاویر و تغییرات بر روی آن هاست (منظور از تغییرات همان استفاده از Decorator هاست). نمونه های دیگری از کارها، عملیات اختیاری دیگری را بر عهده می گیرند که از اهمیت کمتری برخوردار هستند.  برخی از این عملیات می تواند بخشی از Component اصلی و یا رابط های مربوط به آن باشند. در حالی که برخی از رفتار ها و مشخصه ها  تنها به Decorator های خاصی اضافه می شوند. برنامه نویس می تواند هر یک از عملیات را به صورت جداگانه بر روی هر یک از اجزایی (که Decorator برروی آن تاثیر گذاشته باشد و یا نگذاشته باشد.) که به آن دسترسی دارد، فراخوانی کند.

پیاده سازی:
ویژگی اصلی الگوی Decorator آن است که به منظور توزیع رفتار به اشیاء به وراثت تکیه نمی کند. اگر کلاس Tag موظف است تا از کلاس Photo ارث بری کند، که بتواند یک یا چند متد به آن اضافه کند، کلاس های Tag تمامی خصوصیات مربوط به کلاس Photo را با خود حمل خواهند کرد، به همین دلیل باعث ایجاد کلاس های سنگین خواهد شد. در عوض، داشتن کلاس Tag ای که اینترفیس Photo را پیاده سازی کرده و رفتار های مختلفی را به آن اضافه کرده است، باعث می شود که اشیاء پایداری از نوع Tag داشته باشیم.
کلاس های Tag همچنین قادر هستند:
  • هر متدی در اینترفیس را که نیاز دارد پیاده سازی کنند و رفتار اولیه Component را نیز تغییر دهند.
  • هر وضعیت و رفتار جدید را که بخواهند به آن اضافه کنند.
  • به هر کدام از اعضا و المان های public آن که از طریق Constructor به کلاس داده شده است دسترسی پیدا کنند.
به مثالی که در ابتدای مقاله آوردیم و تصویر مربوطه نظری دوباره می اندازیم، مثال های این چنینی برای نشان دادن تعامل و ارتباط بین کلاس ها و اشیاء یک الگو و همچنین با استفاده از دیاگرام های UML مناسب و مفید اند. در دنیای واقعی بهینه سازی ها، موارد جنبی و علل متنوع دیگری باعث می شوند که تشخیص، تجسم، تحلیل و پیاده سازی الگو سخت تر شود. همینطور در دنیای واقعی معمولا با اسامی متفاوتی و یا با اشیاء متفاوت تری نسبت به مثال های ارایه شده مواجه خواهیم شد.

		
using System;

class DecoratorPattern 
{

	// Decorator Pattern Judith Bishop Dec 2006
	// Shows two decorators and the output of various
	// combinations of the decorators on the basic component

	interface IComponent 
	{
		string Operation( );
	}

	class Component : IComponent 
	{
		public string Operation () 
		{
			return "I am walking ";
		}
	}

	class DecoratorA : IComponent 
	{
		IComponent component;

		public DecoratorA (IComponent c) 
		{
			component = c;
		}

		public string Operation( ) 
		{
			string s = component.Operation( );
			s += "and listening to Classic FM ";
			return s;
		}
	}

	class DecoratorB : IComponent 
	{
		IComponent component;
		public string addedState = "past the Coffee Shop ";

		public DecoratorB (IComponent c) 
		{
			component = c;
		}

		public string Operation ( ) 
		{
			string s = component.Operation ( );
			s += "to school ";
			return s;
		}

		public string AddedBehavior( ) 
		{
			return "and I bought a cappuccino ";
		}
	}

	class Client 
	{

		static void Display(string s, IComponent c) 
		{
			Console.WriteLine(s+ c.Operation( ));
		}

		static void Main( ) 
		{
			Console.WriteLine("Decorator Pattern\n");

			IComponent component = new Component( );
			Display("1. Basic component: ", component);
			Display("2. A-decorated : ", new DecoratorA(component));
			Display("3. B-decorated : ", new DecoratorB(component));
			Display("4. B-A-decorated : ", new DecoratorB(
			new DecoratorA(component)));
			// Explicit DecoratorB
			DecoratorB b = new DecoratorB(new Component( ));
			Display("5. A-B-decorated : ", new DecoratorA(b));
			// Invoking its added state and added behavior
			Console.WriteLine("\t\t\t"+b.addedState + b.AddedBehavior( ));
		}
	}
}

/* Output
Decorator Pattern

1. Basic component: I am walking
2. A-decorated : I am walking and listening to Classic FM
3. B-decorated : I am walking to school
4. B-A-decorated : I am walking and listening to Classic FM to school
5. A-B-decorated : I am walking to school and listening to Classic FM
past the Coffee Shop and I bought a cappuccino
*/
 در این مثال ، ما یک اینترفیس IComponent داریم و یک کلاس ساده Component که آن اینترفیس را پیاده سازی کرده است. همچنین دو Decorator داریم که آنها نیز اینترفیس IComponent را پیاده سازی کرده اند. هر یک از این Decorator ها یک IComponent در خود تعبیه کرده اند که قرار هست همان عمل Decoration روی آنها انجام شود، یعنی آن دو اشیائی هستند که هدف Decoration قرار خواهند گرفت. DecoratorA نسبتا ساده است که متد Operation را هم به روشی ساده پیاده سازی کرده است، به این ترتیب که ابتدا متد Operation کامپوننتی را که در خود ذخیره کرده است فراخوانی کرده و سپس مقداری را به رشته ای که آن برگردانده است می چسباند. DecoratorB دقیق تر و گسترده تر است.  و پیاده سازی داخل کلاس را به شیوه دیگری انجام داده است، اما یک مشخصه addedState و نیز متد AddedBehavior را نیز اضافه کرده است. در هر دو متد های Operation ، Operation مربوط به Component زودتر فراخوانی می شود، البته این کار جزو الزامات مرتبط با الگو نیست. این کار فقط برای نمایش یک خروجی خوانا تر انجام شده است.

کلاسی که قرار هست از این الگو برای اهداف مشخص خود استفاده کند، موظف است تا Component ها و Decorator ها را با پیکربندی های متفاوتی ایجاد کند، نتایج فراخوانی عملیات مربوطه در هر کدام از موارد را نمایش دهد. شماره های 2 و 3 در متد main، عمل Decoration را روی کلاس اصلی Component به روش متفاوتی انجام می دهد، همانطور که در خروجی نیز یا همان شماره های 2 و 3 مشخص شده است. شماره های 4 و 5 ، دو  Decoration  با نام A و B را با ترتیب متفاوت اعمال می کنند. در موارد با شماره های 2 تا 4 ، اشیاء Decorator ، نمونه سازی شده ، بلافاصله استفاده شده و سپس از بین می روند. در مورد شماره 5، ما شیء DecoratorB را ایجاد می کنیم و نمونه شیء مربوطه را در یک متغیر با همان نوع (به جای IComponent) نگهداری می کنیم، بنابرایم قادر خواهیم بود رفتار جدید را فراخوانی کنیم.

		
DecoratorB b = new DecoratorB(new Component( ));
Display("5. A-B-decorated : ", new DecoratorA(b));
// Invoking its added state and added behavior
Console.WriteLine("\t\t\t"+b.addedState + b.AddedBehavior( ));
5. A-B-decorated : I am walking to school and listening to Classic FM
past the Coffee Shop and I bought a cappuccino	

در اینجا ما با سه شیء سر و کار داریم:
شیء b که به طور صریح  معرفی (Declare) شده است، و DecoratorA و اشیائی از نوع Component که به طور ضمنی معرفی شده اند.  متد  Display (در کلاس Client)، شیئ DecoratorA را گرفته و متد Operation آن را فراخوانی می کند. این عملیات آن را به درون شیء DecoratorB می برد. در متد Operation مربوط به DecoratorB ، شیئی که DecoratorB با آن ساخه شده است (در کلاس Constructor خوراک کلاس شده است)، خودش متد Operation ای دارد که آن متد فراخوانی شده است، و رشته اولیه "I Am Walking" بازگشت داده شده است. در ادامه، DecoratorB مواردی را را در رابطه با رفتن به مدرسه به آن اضافه می کند،  و سپس DecoratorA مواردی را در رابطه با "Listening to Classic FM" به آن اضافه می کند. این کار فراخوانی متد Display را کامل می کند. همانطور که در خروجی که ابتدای آن شماره 5 قرار دارد، می بینیم،  نتیجه برعکس خروجی سطر بالاتر از خودش است، به این دلیل که Decorator ها با ترتیب متفاوتی  ایجاد شده اند.
با این وجود، این همه ماجرا نیست. Decorator ها می توانند رفتار های جدید نیز تعریف کنند، رفتار هایی که قادر هستند به صراحت فراخوانی شوند.  که ما در سطر آخر متد main این کار را کرده ایم، و نتیجه آن را نیز در  خط آخر خروجی مشاهده می کنیم(که در مورد خرید یک کاپوچینو است). توضیحاتی که داده شده به طور خلاصه نشان می دهد که چگونه Decorator ها کار می کنند. Decorator ها به ویژگی های زبان های پیشرفته نیاز ندارند. آنها به مجموعه گردآوری شده اشیاء و پیاده سازی اینترفیس ها متکی هستند.

مثال: Decorator تصویر
در این بخش بررسی خواهیم کرد که چگونه می توانیم مطابق با تصویر اولیه نمایش داده شده، یک Decorator تصویر  پیاده سازی کنیم. برای اینکه تاکید کرده باشیم که کلاس اصلی Component قبلا نوشته شده است و امکان ویرایش آن برای ما وجود ندارد، کدمان را در یک فضای نام (namespace)  دیگری قرار می دهیم.

using System.Windows.Forms;
namespace Given 
{
	// The original Photo class
	public class Photo : Form 
	{
		Image image;
		public Photo ( ) 
		{
			image = new Bitmap("jug.jpg");
			this.Text = "Lemonade";
			this.Paint += new PaintEventHandler(Drawer);
		}
		
		public virtual void Drawer(Object source, PaintEventArgs e) 
		{
			e.Graphics.DrawImage(image,30,20);
		}
	}
}		
کلاس Photo از  System.Windows.Forms به ارث می برد تا بتوان آن را نمایش داد. سازنده کلاس، متد Drawer را به عنوان ورودی رویداد PaintEventHandler قرار داده است، که وقتی فرم را با متد Main فراخوانی کردیم، متد مربوطه فعال گردد.

متد run برنامه پنجره را به این روش راه اندازی خواهد کرد، بنابراین برنامه نویس به روش زیر می تواند این برنامه را فراخوانی کند.

static void Main ( ) 
{
	// Application.Run acts as a simple client
	Application.Run(new Photo( ));
}	
اکنون بدون تغییر چیزی در کلاس Photo ، می توانیم شروع به اضافه کردن Decorator ها نماییم. اولین Decorator یک حاشیه آبی رنگ دور تصویر خواهد کشید ( فرض ما بر این است که بسیاری از مشخصه های لازم تصویر برای کشیدن این خط برای ما مشخص است تا بتوانیم راحت تر این موارد را شرح دهیم)

// This simple border decorator adds a colored border of fixed size
class BorderedPhoto : Photo 
{
	Photo photo;
	Color color;
	public BorderedPhoto (Photo p, Color c) 
	{
		photo = p;
		color=c;
	}
	
	public override void Drawer(Object source, PaintEventArgs e) 
	{
		photo.Drawer(source, e);
		e.Graphics.DrawRectangle(new Pen(color, 10),25,15,215,225);
	}
}	
توجه داشته باشید که این کد با الگوی توضیح داده شده  در دیاگرام UML تفاوت دارد و از اینترفیس IComponent خبری نیست. این کد از نظر فنی کاملا مورد قبول است،  Decorator ها می توانند مستقیما از کلاس Component ارث بری کنند همچنین یک شیء از همان کلاس را درون خود داشته باشند. حال می بینیم که متد Drawer ، عملی است که از درون Decorator و Decorator بعدی فراخوانی می شود.  اگر این کار مورد نظر ما نبوده است، و قرار بوده باشد که تغییراتی در کلاس ایجاد کنیم، پس نمی توانیم از کلاس Component ارث بری کنیم و باید به سراغ IComponent برویم. چون در این صورت واسط مورد نیاز خواهد شد. این کار در مثال زیر نمایش داده شده است.

Decorator تگ، از یک ساختار مشابه پیروی می کند، بنابراین می توانیم به صورت ترکیبی زیر  عمل کنیم.

// Compose a photo with two tags and a blue border
foodTag = new Tag (photo, "Food", 1);
colorTag = new Tag (foodTag, "Yellow", 2);
composition = new BorderedPhoto(colorTag, Color.Blue);
Application.Run(composition);
نتیجه این دنباله از کد ها و استفاده از Decoraotor ها ، شکل اولیه نمایش داده شده در مقاله است، توجه داشته باشید که Decorator ها با پارامتر های مختلفی ایجاد ( نمونه سازی ) می شوند، تا بتوانند نتیجه و تاثیر متفاوتی داشته باشند. شیء foodTag  پارامتر دومی با محتوای "Food" دارد، و پارامتر دوم (colorTag)  نیز "Yellow" است.
در پایان،  وضعیت (state) و رفتار (behavior) اضافه شده در کلاس برچسب (tag) را بررسی می کنیم. کلاس tag ، یک آرایه استاتیک و یک شمارنده تعریف می کند که شمارنده، اسامی tag ها را به ترتیب ورود ذخیره می کند. هر شیئ از Decorator های Tag، می تواند متد ListTags را صدا بزند تا همه  tag هایی که در برنامه به کار رفته اند را نمایش دهد.  کد زیر به طور کامل این موضوع را نمایش می دهد.


using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Collections.Generic;
using Given;
// Decorator Pattern Example Judith Bishop Aug 2007
// Draws a single photograph in a window of fixed size
// Has decorators that are BorderedPhotos and TaggedPhotos
// that can be composed and added in different combinations
namespace Given 
{
	// The original Photo class
	public class Photo : Form 
	{
		Image image;
		public Photo ( ) 
		{
			image = new Bitmap("jug.jpg");
			this.Text = "Lemonade";
			this.Paint += new PaintEventHandler(Drawer);
		}
		
		public virtual void Drawer(Object source, PaintEventArgs e) 
		{
			e.Graphics.DrawImage(image,30,20);
		}
	}
}

class DecoratorPatternExample 
{
	// This simple BorderedPhoto decorator adds a colored border of fixed size
	class BorderedPhoto : Photo 
	{
		Photo photo;
		Color color;
		public BorderedPhoto (Photo p, Color c) 
		{
			photo = p;
			color=c;
		}
		
		public override void Drawer(Object source, PaintEventArgs e) 
		{
			photo.Drawer(source, e);
			e.Graphics.DrawRectangle(new Pen(color, 10),25,15,215,225);
		}
	}

	// The TaggedPhoto decorator keeps track of the tag number which gives it
	// a specific place to be written
	class TaggedPhoto : Photo 
	{
		Photo photo;
		string tag;
		int number;
		static int count;
		List  tags = new List  ( );
		public TaggedPhoto(Photo p, string t) 
		{
			photo = p;
			tag = t;
			tags.Add(t);
			number = ++count;
		}
		
		public override void Drawer(Object source, PaintEventArgs e) 
		{
			photo.Drawer(source,e);
			e.Graphics.DrawString(tag,
			new Font("Arial", 16),
			new SolidBrush(Color.Black),
			new PointF(80,100+number*20));
		}
		
		public string ListTaggedPhotos( ) 
		{
			string s = "Tags are: ";
			foreach (string t in tags) s +=t+" ";
			return s;
		}
	}

	static void Main ( ) 
	{
		// Application.Run acts as a simple client
		Photo photo;
		TaggedPhoto foodTaggedPhoto, colorTaggedPhoto, tag;
		BorderedPhoto composition;
		// Compose a photo with two TaggedPhotos and a blue BorderedPhoto
		photo = new Photo( );
		Application.Run(photo);
		foodTaggedPhoto = new TaggedPhoto (photo,"Food");
		colorTaggedPhoto = new TaggedPhoto (foodTaggedPhoto,"Yellow");
		composition = new BorderedPhoto(colorTaggedPhoto, Color.Blue);
		Application.Run(composition);
		Console.WriteLine(colorTaggedPhoto.ListTaggedPhotos( ));
		// Compose a photo with one TaggedPhoto and a yellow BorderedPhoto
		photo = new Photo( );
		tag = new TaggedPhoto (photo,"Jug");
		composition = new BorderedPhoto(tag, Color.Yellow);
		Application.Run(composition);
		Console.WriteLine(tag.ListTaggedPhotos( ));
	}
}

/* Output
TaggedPhotos are: Food Yellow
TaggedPhotos are: Food Yellow Jug
*/

نکته مهم در مورد الگوی Decorator این است که، این الگو مبتنی بر اشیاء جدیدی است  که این اشیاء توسط مجموعه ای از عملیات متعلق به خود آن اشیاء ایجاد می شوند. برخی از این اشیاء می توانند به ارث برده شوند،  اما فقط در یک سطح این عمل انجام می گیرد. به عنوان نمونه، زمانی که برنامه Decorator تصویر را پیاده سازی می کردیم، می توانستیم، مشخصه های کلاس Windows Form مانند ارتفاع و عرض را از طریق شیئ Decorator تغییر دهیم. برای خود Component و Decorator سطح اول این روش عملی است. اما  به مجرد اینکه سطح دوم Decorator ها هویدا شوند، تغییرات اعمال نمی گردد. دلیل آن هم از نمودار زیر که در بالا هم نمایش دادیم، پیداست.

Food Uml Diagram

Decorator اول یک ارجاع به شیء Windows Form را نگهداری می کند، اما Decorator سطح دوم چنین کاری را انجام نمی دهد. برای چنین سناریو هایی از الگو های دیگری مانند الگوی Strategy استفاده می شود. در برنامه Decorator تصویر که مثال زدیم، ما در واقع هیچ رفتاری (behavior) را اضافه نکردیم، در واقع آن را Override کردیم. وقتی یک برنامه نوشته می شود،  متد Run در حقیقت، از مدل واقعه (Event Model) برای فراخوانی ضمنی PaintEventHandler استفاده می کند. البته این کار به ما این فرصت را نمی دهد که بتوانیم متد های دیگر را به طور صریح فراخوانی کنیم. البته متد هایی مانند: MouseMove و OnClick می توانند پیاده سازی شوند، در این صورت رفتار های دیگر درون Decorator ها ، معنی پیدا می کنند.

کاربرد الگو:

در اینجا به 4 کاربرد این الگو در دنیای واقعی اشاره می کنیم:

  • همانگونه که در مثال کوچکمان دیدید، الگوی Decorator، در دنیای گرافیک نقش خوبی بازی می کند، به همانگونه نیز در دنیای صدا و تصویر موثر است. برای مثال، video streaming ، که می تواند با سرعت های مختلفی عمل فشرده سازی را انجام دهد، و صدا هم می تواند به عنوان ورودی سیستم های همزمان ترجمه استفاده گردد.
  • در سطوح کاربردی و واقعی تر، برای استفاده از الگوی Decorator در حوزه برنامه های مبتنی بر I/O (ورودی/خروجی) در زبان C# باید سلسله مراتب زیر را در نظر داشته باشیم:
    System.IO.Stream
    System.IO.BufferedStream
    System.IO.FileStream
    System.IO.MemoryStream
    System.Net.Sockets.NetworkStream
    System.Security.Cryptography.CryptoStream
    کلاس های داخلی و کلاس های لایه های زیرین،  عمل Decoration را روی stream انجام می دهند، به این دلیل که از آن ارث بری می کنند. و همچنین، یک نمونه از stream را دارند که زمان ساخت شیء ،  ایجاد می شود. و بسیاری از این ویژگی ها و متد ها به همین نمونه مربوط می شوند.
  • در دنیای امروز دستگاه های همراه ( تلفن همراه و غیره )، مرورگرهای وب و سایر برنامه های کاربردی این دستگاه ها،  از الگوی Decorator نیز برای بهبود سازی و پیشرفت خود استفاده کرده اند. برای نمونه، آنها می توانند صفحه های نمایشی تولید کنند که برای صفحات کوچکتر مناسب هستند، این صفحات شامل نوار پیمایش هستند و دیگر بنر های متعدد تبلیغاتی داخل صفحه را نمایش نمی دهند، بنر هایی که نمایش آنها در صفحات مربوط به دستگاه های دسکتاپ مناسب به نظر می رسد.
  • الگوی Decorator به قدری کاربردی و مفید است که اکنون، کلاس های Decorator از پیش ایجاد شده ای از دات نت نسخه 3 به بعد تعبیه شده است، یکی از آنها کلاس System.Windows.Controls است که کلاس پایه ای را ایجاد می کند که این کلاس برای عناصری استفاده می شوند که می خواهند افکت های مختلفی مانند حاشیه را به عناصر فرزند آن تخصیص دهند.
کی بهتر است از الگوی Decorator استفاده کنیم:

  • کلاس اصلی (Component) وجود دارد و  برای استفاده از آن نمی توانیم از امکان Subclassing بهره بگیریم.
  • بخواهیم وضعیت، رفتار جدید و مازادی را به صورت پویا به کلاس اصلی ( به یک شیء ) اضافه کنیم.
  • بخواهیم در برخی از اشیاء موجود در کلاس، تغییراتی ایجاد کنیم که این تغییرات بقیه کلاس ها و اشیاء را تحت تاثیر قرار ندهد.
  • بخواهیم از ایجاد زیر کلاس های متعدد مبتنی بر نیاز اجتناب کنیم، چون ایجاد Subclass های مختلف از کلاس اصلی باعث افزایش چشمگیر کلاس ها می شود.
  • بخواهیم جایگزین الگوهای دیگری کنیم:
    • جایگزین الگوی Adapter، که یک رابط کاربری بین کلاس های مختلف ایجاد می کند.
    • جایگزین الگوی Proxy، که به طور اختصاصی دسترسی به اشیاء را کنترل می کند.
    • جایگزین الگوی Composite، که یک شیئ  را بصورت مجموعه استفاده می کند، بدون اینکه حتی از اینترفیس مربوطه ارث بری کرده باشد.
    • جایگزین الگوی Strategy، که شیء اصلی را تغییر می دهد.

منبع:

C# 3.0 Design Patterns - E-book


پست های مرتبط

نام را وارد کنید
تعداد کاراکتر باقیمانده: 1000
نظر خود را وارد کنید