الگوی آذینگر

در برنامه‌نویسی شئ گرا، الگوی آذینگر یا دکوراتر (decorator) یک الگوی طراحی است که امکان افزودن رفتار (behavior) به یک شئ، را به‌طور پویا (dynamic) یا ایستا (static) فراهم می‌سازد بی آنکه رفتار اشیاء دیگر از همان کلاس (که شیء مورد بحث از آن ساخته شده) دست‌خوش تغییر شوند.[۱] الگوی طراحی آذینگر معمولاً برای پایبندی به قاعدهٔ تک وظیفه‌ای مورد استفاده قرار می‌گیرد چرا که این الگوی طراحی، امکان تقسیم عملکردها (functionality) بین کلاس‌های مختلف که هر کدام دغدغه‌های (concern) خاص را پوشش می‌دهند، فراهم می‌سازد.[۲] به عنوان یک مثال اولیه برای آشنایی بیشتر با این الگو، یک کلاس را در نظر بگیرید که یک رشته را در ورودی دریافت می‌کند و صرفا آن را در خروجی نمایش می‌دهد. حالا می‌توانیم با استفاده از الگوی آذینگر، کارایی‌های دیگری (نظیر spell checking و یا highlight کردن متن) به این کلاس اضافه کنیم بدون اینکه رفتار اولیه این کلاس عوض شود و تغییری کند. این کار از طریق نوشتن یک سری تابع کمکی و در نهایت پیچیدن تابع اولیه (wrapping) با این تابع‌های جدید انجام می‌شود.

هدف[ویرایش]

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

  1. ایجاد یک زیر کلاسِ آذینگر از کلاس اصلی
  2. افزودن یک اشاره‌گر به صورت فیلد (field) که به کلاس اصلی اشاره می‌کند.
  3. ارسال یک شئ از طریق سازنده (constructor) به فیلد برای مقدار دهی به اشاره‌گر.
  4. در کلاس آذینگر از تمام متدهای(Method) کلاس آذین‌شده یا همان کلاس اصلی باید استفاده کرد.
  5. در کلاس آذینگر، هر متد از کلاس اصلی را که می‌بایست عملکردش اصلاح شود (گسترش یابد) باید override کرد.
نمودار UML(زبان مدلسازی یکپارچه) برای کلاس آذینگر

این الگو طراحی شده‌است تا چندین آذینگر به راحتی بتوانند برهم سوار شوند (دور هم بپیچند) و هر بار که یک آذینگر به دور آذینگر دیگر می‌پیچد، یک عملکرد جدید به متدهای override شده افزوده می‌شود.

باید دقت شود که آذینگرها و کلاس اصلی مجموعه‌ای از ویژگی‌ها را به اشتراک می‌گذراند. در شکل قبل، اسلوب (متد) operation() هم در کلاس‌های آذین‌شده و هم درکلاس‌های آذین نشده حضور دارد.

اجزاء آذین (مانند اسلوب‌ها (متدها)، فیلدها یا اعضای دیگر) معمولاً از طریق یک رابط یا همان اینترفیس(interface)، یا از طریق حواله (استفاده از شئ یک کلاس در شئ کلاس دیگر)، یا ارث‌بَری از یک کلاس، که هم اشیاء آذینگر و هم اشیاء آذین‌شده از آن‌ها استفاده می‌کنند، تعریف می‌شوند. در مثال قبلی کلاس ConcreteComponent و کلاس Decorator و تمامی کلاس‌هایی که از آن ارث می‌برند، از کلاس Component نیز ارث می‌برند.

الگوی طراحی آذینگر جایگزینی برای ارث‌بری از طریق زیر کلاس شدن، می‌باشد. وراثت، در زمان کامپایل رفتار(behavior) را به یک کلاس و متعاقباً اشیاء آن کلاس اضافه می‌کند ولی آذینگری، امکان افزودن رفتارهای جدید را در زمان‌اجرا آنهم برای اشیاء دلخواه برنامه‌نویس فراهم می‌سازد.

در بسیاری از زبان‌های برنامه‌نویسی شئ‌گرا، امکان ایجاد کلاس‌ها در زمان اجرا وجود ندارد و در بسیاری از مواقع پیش‌بینی تمامی حالات توسعهٔ عملکرد (functionality extension) دشوار است. توسعهٔ عملکرد به معنای ایجاد یک کلاس برای هر ترکیب ممکن می‌باشد. آذینگرها اشیائی هستند که در زمان‌اجرا ساخته می‌شوند، و می‌توان آن‌ها را با یکدیگر ترکیب کرد. در جاوا و دات‌نت برای پیاده‌سازی جریان ورودی/خروجی(I/O Streams)، از الگوی آذینگر استفاده می‌شود.

شرح مسئله[ویرایش]

به عنوان مثال، یک پنجره را در سامانهٔ پنجره‌ای در نظر بگیرید. برای ایجاد قابلیت اسکرول کردن نوار نورد برای پنجره‌ها ممکن است بخواهیم نوار نورد افقی یا عمودی ایجاد کنیم. فرض کنید که به ازای هر پنجره یک شئ از کلاس Window داریم (این کلاس موجبات به نمایش درآمدن و عمل کردن پنجره را در برنامه فراهم می‌کند) و فرض کنید که این کلاس عملکردی برای افزودن نوار نورد ندارد. ممکن است برنامه‌نویسی برای افزودن این قابلیت به برنامه کلاسی با نام ScrollingWindow به وجود آورد که از کلاس Windows فرزند این کلاس است و از این کلاس ارث می‌برد، یا اینکه فرض کنید برنامه‌نویس به جای اینکار کلاسی با نام

UML diagram for the window example

ScrollingWindowDecorator می‌سازد که به اشیاء کلاس Window قابلیت جدید مربوط به نوار نبرد را می‌افزاید. همان‌طور که می‌بینیم، تا اینجای کار هر دو روش، صحیح و کارآمد هستند.

حالا، فرض کنید می‌خواهیم به پنجره‌ها حاشیه اضافه کنیم، در این موارد هم فرض کنید که کلاس Windows قابلیت اینکار را ندارد. حالا با طرح این قابلیت جدید روش ارث‌بری دچار مشکل می‌شود چرا که فرض کنید کسی می‌خواهد بعضی پنجره‌ها دارای حاشیه و دارای قابلیت اسکرول باشند و بعضی فقط دارای قابلیت اسکرول باشند و بعضی دیگر فقط دارای حاشیه باشند در اینصورت اگر بخواهیم از روش ارث‌بری استفاده کنیم باید کلاس‌های زیر را به بسازیم: WindowWithBorder و ScrollingWindowsWithBorder (هر دوی این کلاس‌ها فرزند کلاس Window هستند) در این صورت می‌توان پیش‌بینی کرد که با افزودن هر قابلیت جدید باید چندین زیر کلاس از کلاس Window بسازیم. برای حل این مشکل می‌توان به جای روش ارث‌بری از الگوی طراحی آذینگر استفاده کرد. در این روش به ازای هر قابلیت جدید (قابلیت اسکرول، قابلیت داشتن حاشیه و …) فقط لازم است، یک کلاس آذینگر درست کنیم. مثلاً در این مثال می‌توانیم دو کلاس آذینگر با نام‌های BorderedWindowDecorator و ScrollingWindowDecorator بسازیم و با استفاده از این دو کلاس در زماان اجرا اشیاء از کلاس Widnow را آذین کنیم. دقت کنید کنید که در صورتی که این قابلیت‌ها باید به تمام پنجره‌ها افزوده شود می‌توان، کلاس والد یعنی Window را تغییر داد تا با نیازهای ما همخوانی داشته باشد. از طرف دیگر گاهی (مثلاً زمانی که از چارچوب‌های (Framework) خارجی استفاده می‌کنیم)، تغییر و دست بردن در کلاس والد قانونی و راحت نیست، بنابراین در این مواقع استفاده از این الگوی طراحی می‌تواند کمک بزرگی به برنامه‌نویس باشد.

توجه داشته باشید که در مثال پنجره که آورده شد، کلاس SimpleWindow و دو کلاس آذینگر رابط window را که دارای دو اسلوب (متد) draw() و getDescription() است، پیاده‌سازی می‌کنند.

مثال‌ها[ویرایش]

مثال اول (پنجره/نوار نبرد)[ویرایش]

کلاس زیر نحوهٔ استفاده از آذینگر را در مثال پنجره/نوار نبرد، نشان می‌دهد.

// The Window interface class  public interface Window {      public void draw(); // برای کشیدن پنجره      public String getDescription(); // رشته‌ای حاوی توضیح در مورد قابلیت‌ها پنجره را برمی‌گرداند  }      // پیاده‌سازی رابط فوق به صورت یک پنجرهٔ بدون نوار نبرد  class SimpleWindow implements Window {      public void draw() {      // کدهای مربوط به کشیدن پنجره      }      public String getDescription() {          return "simple window";      }  } 

کلاس‌های زیر آذینگرهای کلاس Window و کلاس‌های آذینگر دیگر، هستند.

// کلاس تجریدی آذینگر، دقت کنید که رابط ویندو را یپاده‌سازی می‌کند  abstract class WindowDecorator implements Window {      protected Window windowToBeDecorated; // the Window being decorated      public WindowDecorator (Window windowToBeDecorated) {          this.windowToBeDecorated = windowToBeDecorated;      }      public void draw() {          windowToBeDecorated.draw(); //Delegation      }      public String getDescription() {        return windowToBeDecorated.getDescription(); //Delegation      }  }  // The first concrete decorator which adds vertical scrollbar functionality  class VerticalScrollBarDecorator extends WindowDecorator {      public VerticalScrollBarDecorator (Window windowToBeDecorated) {          super(windowToBeDecorated);      }      @Override     public void draw() {          super.draw();          drawVerticalScrollBar();      }      private void drawVerticalScrollBar() {      // Draw the vertical scrollbar      }      @Override     public String getDescription() {          return super.getDescription() + ", including vertical scrollbars";      }  }  // The second concrete decorator which adds horizontal scrollbar functionality  class HorizontalScrollBarDecorator extends WindowDecorator {      public HorizontalScrollBarDecorator (Window windowToBeDecorated) {      super(windowToBeDecorated);      }      @Override     public void draw() {          super.draw();         drawHorizontalScrollBar();      }      private void drawHorizontalScrollBar() {      // Draw the horizontal scrollbar      }      @Override     public String getDescription() {          return super.getDescription() + ", including horizontal scrollbars";      }  } 

در ذیل این مطلب برنامه‌ای آورده شده‌است که در آن یک نمونه (شئ) از کلاس SimpleWindow ساخته می‌شود و این کلاس با استفاده از کلاس‌های آذینگر با نام‌های HorizontalScrollBarDecorator و VerticalScrollBarDecorator آذین‌می‌شود.

public class DecoratedWindowTest {  public static void main(String[] args) {  // Create a decorated Window with horizontal and vertical scrollbars  Window decoratedWindow = new HorizontalScrollBarDecorator (  new VerticalScrollBarDecorator (new SimpleWindow()));  // Print the Window's description  System.out.println(decoratedWindow.getDescription());  }  } 

در زیر هم کلاس بررسی صحت با استفاده از Junit آورده شده‌است:

import static org.junit.Assert.assertEquals;  import org.junit.Test;  public class WindowDecoratorTest {  @Test  public void testWindowDecoratorTest() {  Window decoratedWindow = new HorizontalScrollBarDecorator(new VerticalScrollbarDecorator(new SimpleWindow()));  // assert that the description indeed includes horizontal + vertical scrollbars  assertEquals("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 decorator  public interface Coffee {  public double getCost(); // Returns the cost of the coffee  public String getIngredients(); // Returns the ingredients of the coffee  }  // Extension of a simple coffee without any extra ingredients  public class SimpleCoffee implements Coffee {  @Override  public double getCost() {  return 1;  }  @Override  public String getIngredients() {  return "Coffee";  }  } 

کلاس‌های زیر آذینگرها هستند

// Abstract decorator class - note that it implements Coffee interface  public abstract class CoffeeDecorator implements Coffee {  protected final Coffee decoratedCoffee;  public CoffeeDecorator(Coffee c) {  this.decoratedCoffee = c;  }  public double getCost() { // Implementing methods of the interface  return decoratedCoffee.getCost();  }  public String getIngredients() {  return decoratedCoffee.getIngredients();  }  }  // Decorator WithMilk mixes milk into coffee.  // Note it extends CoffeeDecorator.  class WithMilk extends CoffeeDecorator {  public WithMilk(Coffee c) {  super(c);  }  public double getCost() { // Overriding methods defined in the abstract superclass  return super.getCost() + 0.5;  }  public String getIngredients() {  return super.getIngredients() + ", Milk";  }  }  // Decorator WithSprinkles mixes sprinkles onto coffee.  // Note it extends CoffeeDecorator.  class WithSprinkles extends CoffeeDecorator {  public WithSprinkles(Coffee c) {  super(c);  }  public double getCost() {  return super.getCost() + 0.2;  }  public String getIngredients() {  return super.getIngredients() + ", Sprinkles";  }  } 

در ذیل این متن مثالی برای بررسی صحت برنامهٔ فوق بیان شده‌است در این مثال یک نمونه از کلاس Coffee ساخته شده و با آذینگرهای WithMilk و WithSprinkles آذین می‌شود و قیمت قهوهٔ تولید شده به همراه مخلفات (شیر و …) را محاسبه کرده و مخلفات آن را نمایش می‌دهد.

public class Main {  public static void printInfo(Coffee c) {  System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());  }  public static void main(String[] args) {  Coffee c = new SimpleCoffee();  printInfo(c);  c = new WithMilk(c);  printInfo(c);  c = new WithSprinkles(c);  printInfo(c);  }  } 

خروجی این کد فوق:

Cost: 1.0; Ingredients: Coffee  Cost: 1.5; Ingredients: Coffee, Milk  Cost: 1.7; Ingredients: Coffee, Milk, Sprinkles 

منابع[ویرایش]

  1. Gamma، Erich (۱۹۹۵). Design Patterns. Addison-Wesley Publishing Co, Inc. صص. ۱۷۵ff. شابک ۰-۲۰۱-۶۳۳۶۱-۲.
  2. «How to Implement a Decorator Pattern». ۲۰۱۵-۰۷-۰۷. بایگانی‌شده از اصلی در ۷ ژوئیه ۲۰۱۵. دریافت‌شده در ۲۷ مه ۲۰۱۹.