آموزش دیزاین پترن Decorator با طعم پیتزا

خب این پترن بسیار جذاب همیشه درنگاه اول پیچیده میاد و آدم نمیتونه کاربرد اصلیشو توی دنیای واقعی و توی تسک‌های روزمره که میزنه پیدا کنه. با سرچ ها و تمرینایی که خودم کردم به این مثال رسیدم و امیدوارم بتونه شمارو هم تو این الگو راه بندازه.

اول بریم سراِغ مفاهیم کلی که هرجا سرچ کنید هم نوشته مثلا اینکه این پترن یک پترن Structural است و برای این استفاده میشود که ما در runtime بتونیم یک آبجکت دیگر رو ارث بری کنیم بدون اینکه مستقیما در کد اون رو extend کرده باشیم. میدونم که اگر اولین باره که این جمله‌ها رو میشنوید اصلا تصوری ازشون ندارید اما بامن همراه باشید!

بریم سراغ مثالمون. فرض کنید یه پیتزا فروشی زدیم :). توی پیتزا فروشیمون یه پیتزا normal داریم که هرکی سفارش بده و چیز خاصی نخواد اونو براش میبریم. اما دو نوع پیتزا دیگه‌ام داریم پپرونی و مخصوص که تفاوتشون برای ما تو کد فقط در قیمت و اسمیست که توی فاکتور نشون داده میشه. حالا برای پیاده کردن این مجموعه میتونیم یه کلاس پیتزا داشته باشیم و توش با فیلد مشخص کنیم که نوعش چیه که در این صورت هی باید کلاسمون رو تغییر بدیم که کار خوبی نیست و یا به ازای هر نوع کلاس بنویسیم که به نسبت کار قبلی منطقی تره ولی بیاید با decorator پیادش کنیم که هم یادبگیریم و هم راه حل هوشمند رو ببینیم!

در این الگو ما یک کلاس base داریم که اینجا میشه NormalPizza و هرچنتا کلاس decorator که کلاس base مارو میتونن دکور کنن و بهش چیزای جدید اضافه کنند. اول بریم کلاس بیس رو پیاده کنیم

public interface Pizza {
	int price();
	String name();
}

public class NormalPizza implements Pizza{

	@Override
	public int price() {
              return 10;
         }

	@Override
	public String name() {
		return &quotPizza&quot
         }
}
 

خب اون اینترفیس که اول نوشتیم قلب ماجراس و هم base و هم decorator ها در نهایت این واسط رو پیاده میکنند. مقادیر متدها hardcode شده بدلیل سادگی.

الان هرکی از ما پیتزا بخواد یه pizza بهش میدیم به قیمت ۱۰ (دلار ، ریال ، تومن ...) حالا بریم سراغ دکوراتورها. قبل از پیاده سازیشون باید یک کلاس ابسترکت بسازیم که اولا از pizza ارث بری کنه و دوما کلاس های دکوراتر رو مجبور کنه که در سازنده خودشون یک آبجکت از نوع Pizza بگیرن چون باید بدونیم چیو میخوایم دکور کنیم :

public abstract class PizzaDecorator implements Pizza{
   protect Pizza pizza;
 
   public PizzaDecorator(Pizza pizza){
       this.pizza=pizza;
    }
}
توجه دارید که نمیتونستیم از اینترفیس استفاده کنیم چون اینترفیس نمیتونه سازنده داشته باشه.

حالا همه دکوراتر ها باید از این کلاس ارث بری کنند و کارشون رو انجا بدن. مثلا کلاس PizzaSpecial ما اینجوری خواهد بود :

public class PizzaSpecial extends PizzaDecorator{
  public PizzaSpecial(Pizza pizza){
       super(pizza);
   }

  @Override 	
   public int price() {
               return 5 + this.pizza.price();        
    } 

  @Override
 public String name() {
	return    &quotSpecial &quot + this.pizza.name()
        }
}

حالا ما هر پیتزایی رو به این دکوراتر بدیم اسم فاکتورش رو درست میکنه و قیمتش رو هم اضافه میکنه :

Pizza pizza= new NormalPizza();
Pizza sPizza=new SpecialPizza(pizza);

println(sPizza.name());

خروجی برنامه Special Pizza خواهد بود :)

حالا میتونیم تا هرچندتا دکوراتر دیگه هم براش درست کنیم و اونا به ترتیب اجرا میشن. مثلا دکوراتر آویشن‌(Thyme) :

public class Thyme extends PizzaDecorator{   

public Thyme(Pizza pizza){        super(pizza);    }

    @Override
    public int price() {
              return 2 + this.pizza.price();
        }  

   @Override  public String name() {
 	return  this.pizza.name() + &quot with Thyme&quot
         }
 }

حالا برای استفاده مینویسم :

Pizza pizza= new NormalPizza(); 
Pizza sPizza=new SpecialPizza(pizza);
Pizza sPizzaThyme=new Thyme(sPizza);

  println(sPizzaThyme.name());

که خروجی میشه "Special Pizza with thyme" و اگر قیمت رو هم پرینت کنید میشه ۱۷ و میبیند چقدر میتونیم توسعه بدیم کاررو بدون تغییر تو کلاس‌های دیگر و به این میگن loose coupling :)

حالا میتونید خودتون برای تمرین کلاس پپرونی رو درست کنید و تست کنید و لذت ببرید.

امیدوارم بدردتون خورده باشه سئوالی بود اینجا یا توئیتر هستم.