آشنایی با Decorator Design Pattern

در این پست می خواهیم الگوی طراحی را بررسی کنیم. قطعه کد ها با #C ایجاد شده اند. Decorator pattern به کاربر اجازه می دهد به یک شی موجود قابلیت های جدیدی اضافه کند بدون اینکه ساختار آن را تغییر دهد.

آشنایی با Decorator Design Pattern

Design pattern یک متد استاندارد برای حل مشکلاتی که معمولا اتفاق می افتد می باشد. الگوی طراحی Decorator در دسترسی به چیزی در حالت ارث بری در زمان اجرا به ما کمک می کند.در زمان ارث بری یک کلاس، ما رفتار یک کلاس را تغییر می دهیم و این متد تغییر رفتار تنها در زمان کامپایل امکان پذیر می باشد. در مقابل در زمان decorating یک شی (با استفاده از design pattern) رفتار شی را در زمان اجرا تغییر می دهیم.

حل مساله

برای درک design pattern، درک حل مشکل مهم است.  با یک مثال به درک این موضوع می پردازیم. فرض کنید یک کلاس ساده به نام stack با متدهای push و pop داریم .

public class Stack 
{
    private List<int> list = new List<int>();
    
    virtual public void Push(int value)
    {
        list.Add(value);
    } 

    virtual public int Pop()
    {
        int result  = list.ElementAt(list.Count - 1);
        list.RemoveAt(list.Count - 1);
        return result;
    }
}

و به عنوان سیستم تکامل یافته، ما متوجه نیاز برای یکی از نمونه های stack برای داشتن trace ورودی در متدهای push و pop می باشد. ایده این می باشد که بتوانیم trace ورودی را در دیباگ سیستم ببینیم.

راه حل سریع برای این مساله که بیشتر ما فکر می کنیم ارث بری از stack و فراخوانی  stackWithTrace می باشد. ما می توانیم یک stack جدید در زمانی که یک stack با trace نیاز داریم معرفی کنیم.

//Solution 1 : Inheriting StackWithTrace from Stack

public class StackWithTrace : Stack
{
    public override void Push(int value)
    {
        Console.WriteLine("Push ("+value.ToString()+")");
        base.Push(value);
    }

    public override int Pop()
    {
        int result = base.Pop();
        Console.WriteLine(result.ToString()+":Pop()");
        return result;
    }
}

راه حل اول، ارث بری stachWithTrace از stack می باشد. اما اگر بعضی از کتابخانه های خارجی یا کدهایی که ما نمی توانیم تغییرشان دهیم  stack ایجاد کرده و یک رفرنس یا ارجاع برای استفاده ما از آن ایجاد کند، کار نخواهد کرد. برای تعمیم این موضوع می توانیم یک راه حل مانند، راه حل شماره 1 ارائه دهیم در زمانی که کنترل کمی در نمونه سازی شی داریم، کار نخواهد کرد.

برای حل این موضوع، می توانیم یک stackWithTraceWrapper که از stack ارث بری می کند بسازیم و یک ارجاع به stack را نگه داریم. متدهای push  و pop در stackWithTraceWrapper می توانند متدهای داخلی Pop و Push مربوط به stack را فراخوانی کنند.

//Solution 2 : Inheriting StackWithTraceWrapper from Stack and holding a reference

public class StackWithTraceWrapper : Stack
{
    Stack innerStack;
    public StackWithTraceWrapper(Stack stack)
    {
        innerStack = stack;        
    }

    public override void Push(int value)
    {
        Console.WriteLine("Push ("+value.ToString()+")");
        innerStack.Push(value);
    }

    public override int Pop()
    {
        int result = innerStack.Pop();
        Console.WriteLine(result.ToString()+":Pop()");
        return result;
    }
}

راه حل دوم به خوبی کار می کند. این راه حل می تواند ویژگی هایی را در زمان اجرا به صورت بسته بندی (wrapping) آن  به stack اضافه کند و به عنوان یک stack عمل کند. اما یک مسئله عملکرد در ارتباط با راه حل ما وجود دارد. اگر به stackWithTraceWrapper نگاه کنیم، متوجه می شویم که آن دارای دو نمونه از stack می باشد. یک نمونه ناشی از ارث بری و نمونه دیگر ناشی از متغیر خصوصی innerStack می باشد. زمانی که حافظه چاپ کلاس stack کوچک است، مساله مهمی نمی باشد. مساله مهم، بزرگ شدن حافظه چاپ شی بسته بندی شده می باشد. که حل این مشکل سخت نمی باشد.

شرح راه حل

به سادگی ذکر قرارداد stack از طریق اینترفیس به نام IStack و بسته بندی Istack، مشکل حل می شود. به راه حلی که بدینگونه به آن رسیده ایم، بعنوان decorator در دنیای برنامه نویسی نامیده می شود.

کد

در زیر پیاده سازی decorator design pattern را برای بیانیه مشکل ما  می بینید. شرط لازم برای درک کد زیر داشتن اطلاعات پایه #C و  interface است.

// Solution 3: Decorator design pattern (Stack being decorated)

public interface IStack
{
    void Push(int value);
    int Pop();
}

public class Stack : IStack
{

    public void Push(

    public void Push(int value)
    {
        list.Add(value);
    } 

    public int Pop()
    {
        int result  = list.ElementAt(list.Count - 1);
        list.RemoveAt(list.Count - 1);
        return result;
    }
}

public class StackTraceDecorator : IStack
{
    IStack innerStack;
    public StackTraceDecorator(IStack stack)
    {
         innerStack = stack;
    }
   
    public void Push(int value)
    {
        innerStack.Push(value);
        Console.WriteLine("Push ("+value.ToString()+")");
    }

    public int Pop()
    {
        int result = innerStack.Pop();
        Console.WriteLine(result.ToString()+":Pop()");
        return result;
    }
}

نقاط مورد علاقه

این الگو اغلب هنگام ارتباط با کتابخانه های خارجی استفاده می شود. این الگو سایر مشکلات جالب دیگر بنام Explosion of classes را حل می کند. 

آموزش سی شارپ