الگوی Decorator

سه شنبه 9 دی 1399

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

الگوی Decorator

قبل از بررسی الگوی مربوطه من به عنوان مشتری می خواهم به شما سفارش یک نرم افزار را بدهم.

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

من ابتدا یک abstract class با نام IceCream ایجاد می کنم.

public abstract class IceCream
{
     public abstract int GetPrice();
}

و با توجه به این که سه مدل بستنی دارم در نتیجه سه implementation از abstract class خودم ایجاد می کنم.

public class LargeIceCream : IceCream
{
           public override int GetPrice()
           {
               return 200;
           }
}
public class MediumIceCream : IceCream
{
           public override int GetPrice()
           {
               return (150);
           }
}
public class SmallIceCream : IceCream
{
           public override int GetPrice()
           {
               return (100);
           }
}

حالا زمانی که مشتری از من بستنی بزرگ می خواهد من اینگونه قیمت را برمی گردانم.

public class Program
{
       static void Main(string[] args)
       {
           var largeIceCream = new LargeIceCream();
           largeIceCream.GetPrice();
           Console.ReadKey();
       }
}

تا اینجای کار کدها را با رعایت اصول single responsibility principle و open closed principle نوشتیم. به این صورت که من اگر بخواهم یک بستنی جدید با اندازه جدید ایجاد کنم فقط یک کلاس جدید ایجاد می کنم و آن را implement می کنم، بدون اینکه در کدهای قبلی خودم تغییرات بدهم.

اما بعد از مدتی بستنی فروش تصمیم می گیرد به بستنی های خود اسمارتیز هم اضافه کند و از مشتریان خود سوال می کند که اسمارتیز به بستنی اضافه کند یا خیر. اگر اسمارتیز اضافه کند بیست درصد به قیمت بستنی اضافه خواهد شد.

اگر شما با decorator آشنایی نداشته باشید ممکن است برای انجام این کار سه کلاس با عناوین MediumIceCreamWithSmarties ، LargeIceCreamWithSmarties و SmallIceCreamWithSmarties ایجاد کنید.

اما اگر بستنی فروش مدتی بعد قصد داشته باشد شکلات هم به بستنی های خود اضافه کند چی؟ یعنی به ازای هر اندازه بستنی چهار کلاس اضافه می شود و ممکن است بیشتر و بیشتر شود.

راه حل چیست؟

در decorator ما یک abstract و یکسری کلاس های پیاده سازی شده داریم و همچنین خواسته های دیگری داریم که این خواسته ها باید بتوانند کلاس من را گسترش دهند و من مجبور نشوم کلاس خودم را تغییر دهم. نکته اینجاست که کسی نمی تواند از من اسمارتیز بخرد و اگر میخواهد باید بستنی همراه با اسمارتیز بخرد.

بنابراین الگوی خود را اینگونه پیاده سازی می کنم. ابتدا یک کلاس decorator اضافه می کنم.

public class IceCreamDecorator : IceCream
{
           private readonly IceCream _iceCream;
           public IceCreamDecorator(IceCream iceCream)
           {
               _iceCream = iceCream;
           }
           public override int GetPrice()
           {
               return (_iceCream.GetPrice());
           }
}

و سپس هر تایپی که اضافه می شود را از این کلاس implement می کنم.

public class IceCreamWithSmarties : IceCreamDecorator
{
           public IceCreamWithSmarties(IceCream iceCream) : base(iceCream)
           {
           }
           public override int GetPrice()
           {
               return (base.GetPrice() + (base.GetPrice() * 10 / 100));
           }
}

public class IceCreamWithChocolate : IceCreamDecorator
{
           public IceCreamWithChocolate(IceCream iceCream) : base(iceCream)
           {
           }
           public override int GetPrice()
           {
               return (base.GetPrice() + (base.GetPrice() * 20 / 100));
           }
}

حالا اگر کسی از من بستنی بزرگ همراه با اسمارتیز بخواهد می نویسم:

var largeIceCream = new LargeIceCream();
var iceCreamWithSmarties = new IceCreamWithSmarties(largeIceCream);
iceCreamWithSmarties.GetPrice();

اگر کسی بستنی کوچک همراه شکلات بخواهد:

var smallIceCream = new SmallIceCream();
var iceCreamWithChocolate = new IceCreamWithChocolate(smallIceCream);
iceCreamWithChocolate.GetPrice();

و اگر کسی بستنی متوسط همراه شکلات و اسمارتیز بخواهد:

var mediumIceCream = new MediumIceCream();
var iceCreamWithChocolate = new IceCreamWithChocolate(mediumIceCream);
var iceCreamWithSmarties = new IceCreamWithSmarties(iceCreamWithChocolate);
iceCreamWithSmarties.GetPrice();

بنابراین دیگه مجبور نیستم برای اضافه کردن دو خاصیت به سیستم، کلاس های زیادی را اضافه کنم و فقط با اضافه کردن یک decorator و implement کردن تایپی که قصد داریم اضافه کنیم کار خود را انجام می دهیم. تایپ های قدیمی تغییری نمی کنند و اضافه کردن موارد جدید براحتی انجام می شود. در نتیجه single responsibility principle و open closed principle می باشد و dependency inversion هم انجام شد.

بنابراین قبل از باز کردن یک کلاس و تغییرات در آن به این فکر کنید که احتمالا می توانید آن را decorate کنید.

پایان

محسن فرخی

نویسنده 1 مقاله در برنامه نویسان
  • C#.net
  • 334 بازدید
  • 2 تشکر

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید

نظرات کاربران

برای درج نظر باید وارد سایت شوید