این مقاله نیازمند تمیزکاری است. لطفاً تا جای امکان آنرا از نظر املا، انشا، چیدمان و درستی بهتر کنید، سپس این برچسب را بردارید. محتویات این مقاله ممکن است غیر قابل اعتماد و نادرست یا جانبدارانه باشد یا قوانین حقوق پدیدآورندگان را نقض کرده باشد.
در برنامهنویسی شئ گرا، الگوی آذینگر یا دکوراتر (decorator) یک الگوی طراحی است که امکان افزودن رفتار (behavior) به یک شئ، را بهطور پویا (dynamic) یا ایستا (static) فراهم میسازد بی آنکه رفتار اشیاء دیگر از همان کلاس (که شیء مورد بحث از آن ساخته شده) دستخوش تغییر شوند.[۱] الگوی طراحی آذینگر معمولاً برای پایبندی به قاعدهٔ تک وظیفهای مورد استفاده قرار میگیرد چرا که این الگوی طراحی، امکان تقسیم عملکردها (functionality) بین کلاسهای مختلف که هر کدام دغدغههای (concern) خاص را پوشش میدهند، فراهم میسازد.[۲] به عنوان یک مثال اولیه برای آشنایی بیشتر با این الگو، یک کلاس را در نظر بگیرید که یک رشته را در ورودی دریافت میکند و صرفا آن را در خروجی نمایش میدهد. حالا میتوانیم با استفاده از الگوی آذینگر، کاراییهای دیگری (نظیر spell checking و یا highlight کردن متن) به این کلاس اضافه کنیم بدون اینکه رفتار اولیه این کلاس عوض شود و تغییری کند. این کار از طریق نوشتن یک سری تابع کمکی و در نهایت پیچیدن تابع اولیه (wrapping) با این تابعهای جدید انجام میشود.
هدف
به منظور گسترش (آذین کردن) عملکرد یک شئ به صورت ایستا یا بعضی مواقع به صورت پویا، در زمان اجرا(Runtime) و بهطور مستقل از اشیاء دیگر همان کلاس، از الگوی طراحی آذینگر استفاده میشود. این مهم از طریق ایجاد یک کلاس آذینگر یا کلاس پیچه، که کلاس اصلی (کلاسی که میخواهیم عملکردش را آذین کنیم)، در آن پیچیده میگردد، برقرار میشود. این عمل پیچیدن کلاس اصلی در کلاس آذینگر از طریق مراحل زیر انجام میشود:
ایجاد یک زیر کلاسِ آذینگر از کلاس اصلی
افزودن یک اشارهگر به صورت فیلد (field) که به کلاس اصلی اشاره میکند.
ارسال یک شئ از طریق سازنده (constructor) به فیلد برای مقدار دهی به اشارهگر.
در کلاس آذینگر از تمام متدهای(Method) کلاس آذینشده یا همان کلاس اصلی باید استفاده کرد.
در کلاس آذینگر، هر متد از کلاس اصلی را که میبایست عملکردش اصلاح شود (گسترش یابد) باید override کرد.
این الگو طراحی شدهاست تا چندین آذینگر به راحتی بتوانند برهم سوار شوند (دور هم بپیچند) و هر بار که یک آذینگر به دور آذینگر دیگر میپیچد، یک عملکرد جدید به متدهای override شده افزوده میشود.
باید دقت شود که آذینگرها و کلاس اصلی مجموعهای از ویژگیها را به اشتراک میگذراند. در شکل قبل، اسلوب (متد) operation() هم در کلاسهای آذینشده و هم درکلاسهای آذین نشده حضور دارد.
اجزاء آذین (مانند اسلوبها (متدها)، فیلدها یا اعضای دیگر) معمولاً از طریق یک رابط یا همان اینترفیس(interface)، یا از طریق حواله (استفاده از شئ یک کلاس در شئ کلاس دیگر)، یا ارثبَری از یک کلاس، که هم اشیاء آذینگر و هم اشیاء آذینشده از آنها استفاده میکنند، تعریف میشوند. در مثال قبلی کلاس ConcreteComponent و کلاس Decorator و تمامی کلاسهایی که از آن ارث میبرند، از کلاس Component نیز ارث میبرند.
الگوی طراحی آذینگر جایگزینی برای ارثبری از طریق زیر کلاس شدن، میباشد. وراثت، در زمان کامپایل رفتار(behavior) را به یک کلاس و متعاقباً اشیاء آن کلاس اضافه میکند ولی آذینگری، امکان افزودن رفتارهای جدید را در زماناجرا آنهم برای اشیاء دلخواه برنامهنویس فراهم میسازد.
در بسیاری از زبانهای برنامهنویسی شئگرا، امکان ایجاد کلاسها در زمان اجرا وجود ندارد و در بسیاری از مواقع پیشبینی تمامی حالات توسعهٔ عملکرد (functionality extension) دشوار است. توسعهٔ عملکرد به معنای ایجاد یک کلاس برای هر ترکیب ممکن میباشد. آذینگرها اشیائی هستند که در زماناجرا ساخته میشوند، و میتوان آنها را با یکدیگر ترکیب کرد. در جاوا و داتنت برای پیادهسازی جریان ورودی/خروجی(I/O Streams)، از الگوی آذینگر استفاده میشود.
شرح مسئله
به عنوان مثال، یک پنجره را در سامانهٔ پنجرهای در نظر بگیرید. برای ایجاد قابلیت اسکرول کردن نوار نورد برای پنجرهها ممکن است بخواهیم نوار نورد افقی یا عمودی ایجاد کنیم. فرض کنید که به ازای هر پنجره یک شئ از کلاس Window داریم (این کلاس موجبات به نمایش درآمدن و عمل کردن پنجره را در برنامه فراهم میکند) و فرض کنید که این کلاس عملکردی برای افزودن نوار نورد ندارد. ممکن است برنامهنویسی برای افزودن این قابلیت به برنامه کلاسی با نام ScrollingWindow به وجود آورد که از کلاس Windows فرزند این کلاس است و از این کلاس ارث میبرد، یا اینکه فرض کنید برنامهنویس به جای اینکار کلاسی با نام
ScrollingWindowDecorator میسازد که به اشیاء کلاس Window قابلیت جدید مربوط به نوار نبرد را میافزاید. همانطور که میبینیم، تا اینجای کار هر دو روش، صحیح و کارآمد هستند.
حالا، فرض کنید میخواهیم به پنجرهها حاشیه اضافه کنیم، در این موارد هم فرض کنید که کلاس Windows قابلیت اینکار را ندارد. حالا با طرح این قابلیت جدید روش ارثبری دچار مشکل میشود چرا که فرض کنید کسی میخواهد بعضی پنجرهها دارای حاشیه و دارای قابلیت اسکرول باشند و بعضی فقط دارای قابلیت اسکرول باشند و بعضی دیگر فقط دارای حاشیه باشند در اینصورت اگر بخواهیم از روش ارثبری استفاده کنیم باید کلاسهای زیر را به بسازیم: WindowWithBorder و ScrollingWindowsWithBorder (هر دوی این کلاسها فرزند کلاس Window هستند) در این صورت میتوان پیشبینی کرد که با افزودن هر قابلیت جدید باید چندین زیر کلاس از کلاس Window بسازیم. برای حل این مشکل میتوان به جای روش ارثبری از الگوی طراحی آذینگر استفاده کرد. در این روش به ازای هر قابلیت جدید (قابلیت اسکرول، قابلیت داشتن حاشیه و …) فقط لازم است، یک کلاس آذینگر درست کنیم. مثلاً در این مثال میتوانیم دو کلاس آذینگر با نامهای BorderedWindowDecorator و ScrollingWindowDecorator بسازیم و با استفاده از این دو کلاس در زماان اجرا اشیاء از کلاس Widnow را آذین کنیم. دقت کنید کنید که در صورتی که این قابلیتها باید به تمام پنجرهها افزوده شود میتوان، کلاس والد یعنی Window را تغییر داد تا با نیازهای ما همخوانی داشته باشد. از طرف دیگر گاهی (مثلاً زمانی که از چارچوبهای (Framework) خارجی استفاده میکنیم)، تغییر و دست بردن در کلاس والد قانونی و راحت نیست، بنابراین در این مواقع استفاده از این الگوی طراحی میتواند کمک بزرگی به برنامهنویس باشد.
توجه داشته باشید که در مثال پنجره که آورده شد، کلاس SimpleWindow و دو کلاس آذینگر رابط window را که دارای دو اسلوب (متد) draw() و getDescription() است، پیادهسازی میکنند.
مثالها
مثال اول (پنجره/نوار نبرد)
کلاس زیر نحوهٔ استفاده از آذینگر را در مثال پنجره/نوار نبرد، نشان میدهد.
// The Window interface classpublicinterfaceWindow{publicvoiddraw();// برای کشیدن پنجرهpublicStringgetDescription();// رشتهای حاوی توضیح در مورد قابلیتها پنجره را برمیگرداند}// پیادهسازی رابط فوق به صورت یک پنجرهٔ بدون نوار نبردclassSimpleWindowimplementsWindow{publicvoiddraw(){// کدهای مربوط به کشیدن پنجره}publicStringgetDescription(){return"simple window";}}
کلاسهای زیر آذینگرهای کلاس Window و کلاسهای آذینگر دیگر، هستند.
// کلاس تجریدی آذینگر، دقت کنید که رابط ویندو را یپادهسازی میکندabstractclassWindowDecoratorimplementsWindow{protectedWindowwindowToBeDecorated;// the Window being decoratedpublicWindowDecorator(WindowwindowToBeDecorated){this.windowToBeDecorated=windowToBeDecorated;}publicvoiddraw(){windowToBeDecorated.draw();//Delegation}publicStringgetDescription(){returnwindowToBeDecorated.getDescription();//Delegation}}// The first concrete decorator which adds vertical scrollbar functionalityclassVerticalScrollBarDecoratorextendsWindowDecorator{publicVerticalScrollBarDecorator(WindowwindowToBeDecorated){super(windowToBeDecorated);}@Overridepublicvoiddraw(){super.draw();drawVerticalScrollBar();}privatevoiddrawVerticalScrollBar(){// Draw the vertical scrollbar}@OverridepublicStringgetDescription(){returnsuper.getDescription()+", including vertical scrollbars";}}// The second concrete decorator which adds horizontal scrollbar functionalityclassHorizontalScrollBarDecoratorextendsWindowDecorator{publicHorizontalScrollBarDecorator(WindowwindowToBeDecorated){super(windowToBeDecorated);}@Overridepublicvoiddraw(){super.draw();drawHorizontalScrollBar();}privatevoiddrawHorizontalScrollBar(){// Draw the horizontal scrollbar}@OverridepublicStringgetDescription(){returnsuper.getDescription()+", including horizontal scrollbars";}}
در ذیل این مطلب برنامهای آورده شدهاست که در آن یک نمونه (شئ) از کلاس SimpleWindow ساخته میشود و این کلاس با استفاده از کلاسهای آذینگر با نامهای HorizontalScrollBarDecorator و VerticalScrollBarDecorator آذینمیشود.
publicclassDecoratedWindowTest{publicstaticvoidmain(String[]args){// Create a decorated Window with horizontal and vertical scrollbarsWindowdecoratedWindow=newHorizontalScrollBarDecorator(newVerticalScrollBarDecorator(newSimpleWindow()));// Print the Window's descriptionSystem.out.println(decoratedWindow.getDescription());}}
در زیر هم کلاس بررسی صحت با استفاده از Junit آورده شدهاست:
import staticorg.junit.Assert.assertEquals;importorg.junit.Test;publicclassWindowDecoratorTest{@TestpublicvoidtestWindowDecoratorTest(){WindowdecoratedWindow=newHorizontalScrollBarDecorator(newVerticalScrollbarDecorator(newSimpleWindow()));// assert that the description indeed includes horizontal + vertical scrollbarsassertEquals("simple window, including vertical scrollbars, including horizontal scrollbars",decoratedWindow.getDescription())}}
خروجی برنامه متن زیر است:
"simple window, including vertical scrollbars, including horizontal scrollbars"
به این نکته توجه کنید که متد (اسلوب) getDescription() از دو کلاس آذینگر که در مثال آمدهاند، ابتدا getDescription() پنجرهٔ اصلی یعنی همان شئ از کلاس SimpleWindow را صدا میزند و پس از آن Description خود را پس از آن اضافه میکنند.
مثال دوم (آماده کردن قهوه)
این مثال جاوا، کاربرد آذینگرها را در مثال آمادهکردن قهوه نشان میدهد. در این مثال برای آماده کردن قهوه فقط قیمت و مخلفات قهوه مدنظر هستند.
// The interface Coffee defines the functionality of Coffee implemented by decoratorpublicinterfaceCoffee{publicdoublegetCost();// Returns the cost of the coffeepublicStringgetIngredients();// Returns the ingredients of the coffee}// Extension of a simple coffee without any extra ingredientspublicclassSimpleCoffeeimplementsCoffee{@OverridepublicdoublegetCost(){return1;}@OverridepublicStringgetIngredients(){return"Coffee";}}
کلاسهای زیر آذینگرها هستند
// Abstract decorator class - note that it implements Coffee interfacepublicabstractclassCoffeeDecoratorimplementsCoffee{protectedfinalCoffeedecoratedCoffee;publicCoffeeDecorator(Coffeec){this.decoratedCoffee=c;}publicdoublegetCost(){// Implementing methods of the interfacereturndecoratedCoffee.getCost();}publicStringgetIngredients(){returndecoratedCoffee.getIngredients();}}// Decorator WithMilk mixes milk into coffee.// Note it extends CoffeeDecorator.classWithMilkextendsCoffeeDecorator{publicWithMilk(Coffeec){super(c);}publicdoublegetCost(){// Overriding methods defined in the abstract superclassreturnsuper.getCost()+0.5;}publicStringgetIngredients(){returnsuper.getIngredients()+", Milk";}}// Decorator WithSprinkles mixes sprinkles onto coffee.// Note it extends CoffeeDecorator.classWithSprinklesextendsCoffeeDecorator{publicWithSprinkles(Coffeec){super(c);}publicdoublegetCost(){returnsuper.getCost()+0.2;}publicStringgetIngredients(){returnsuper.getIngredients()+", Sprinkles";}}
در ذیل این متن مثالی برای بررسی صحت برنامهٔ فوق بیان شدهاست در این مثال یک نمونه از کلاس Coffee ساخته شده و با آذینگرهای WithMilk و WithSprinkles آذین میشود و قیمت قهوهٔ تولید شده به همراه مخلفات (شیر و …) را محاسبه کرده و مخلفات آن را نمایش میدهد.