رادکام
الگوی طراحی Decorator در رده الگوهای طراحی Structural قرار دارد. رسالت الگوی
Decorator این است که راهی برای اتصال و تخصیص یک وضعیت یا رفتار جدید به یک
شیء به صورت پویا فراهم کند. خود شیء در جریان این اتصال نیست، یعنی خود شیء نمی
داند که رفتار و یا وضعیت جدیدی به آن متصل و اضافه شده است، به همین دلیل این الگو در
سامانه های در حال توسعه کاربرد بنیادین دارد. یک نقطه کلیدی پیاده سازی در
الگوی Decorator این است که دکوراتورها هر دو کلاس اصلی را به ارث می برند و
همچنین شامل نمونه ای از آن هستند.
همانطور که از نام الگو پیداست، الگوی Decorator یک شیء موجود را گرفته و رفتار
یا وضعیت و یا یک موجودیتی را به آن اضافه می کند. به عنوان مثال تصویر زیر را
در نظر بگیرید. روش های متعددی برای اضافه کردن یک موجودیت یا صفت جدید به یک
تصویر وجود دارد، به عنوان نمونه، اضافه کردن حاشیه به اطراف آن، و یا اضافه
کردن تگ مرتبط با متن تصویر به آن. چنین مواردی که به تصویر اضافه می شوند، می توانند در بالا با پایین
آن نمایش داده شوند.
ترکیبی از شیء اصلی ( در اینجا تصویر اصلی ) و برخی از مطالب و صفات جدید ، یک
شیء جدید ایجاد می کند. در تصویر دوم (تصویر سمت چپ) چهار شیء جدید
وجود دارد که در تصویر اصلی فقط یکی از آنهاست، در قیاس با تصویر اصلی در سمت
راست، شیء ای وجود دارد که حاشیه را ایجاد کرده است، و دو شیء با نام تگ که
محتوای متفاوتی دارند. هر کدام از این اشیاء یک Decorator محسوب می شوند. با
توجه به این نکته که تعداد روش های تزئین (Decoration) عکس ها بی حد و حصر است، می
توانیم انواع گوناگونی از اشیاء جدید را داشته باشیم.
زیبایی این الگو در این است که:
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 |
Photo photo = new Photo( );
Tag foodTag = new Tag (photo, "Food",1);
Tag colorTag = new Tag (foodTag, "Yellow",2);
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 ها به ویژگی های زبان های پیشرفته نیاز ندارند. آنها به مجموعه گردآوری شده اشیاء و پیاده سازی اینترفیس ها متکی هستند.
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);
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 ها هویدا شوند، تغییرات اعمال نمی گردد. دلیل
آن هم از نمودار زیر که در بالا هم نمایش دادیم، پیداست.
منبع:
C# 3.0 Design Patterns - E-book
3,599بازدید
دیدگاه کاربران
هنوز دیدگاهی ثبت نشده است.
شما میتوانید درباره این مقاله، دیدگاه خود را ثبت کنید.