設計模式學習筆記之裝飾者模式

瑜戈發表於2018-10-16

裝飾者模式簡單點說就是:在不必改變原類檔案和使用繼承的情況下,動態地擴充套件一個物件的功能。它是通過建立一個包裝物件,也就是裝飾來包裹真實的物件。

設計關係圖如下:


   

這樣說,肯定比較抽象,下面我們來舉個例子:

假如我想開個麵館(在顯示生活中我真的想開個麵館啊!)。



    

為了能更好的賺錢,我肯定要推出各種各樣的面類菜品。但其實基本全部的面都是由四種基本的面組成的:細面,寬面,刀削麵和貓耳面。在這四種面的基礎上,我們可以加入,青菜,雞蛋,牛肉,組合出青菜細面,雞蛋寬面,牛肉刀削麵,青菜雞蛋貓耳面等等。。現在為了方便我們的前臺收銀的服務員,我們來實現一個訂單收銀系統。



首先我們想到的設計思路肯定是先設計一個食物的父類然後為每一種不同品種的麵條設計一種子類。比如,青菜刀削麵類、牛肉刀削麵類,雞蛋細面類。。。。


這樣設計的壞處就是擴充套件子類太多,不好維護,如果後期有新加入幾種配菜品類,比如羊肉,番茄等等,這就會多出很多類,也不便於擴充套件。而且可能會出現類似於機器學習中的‘維度災難’的類災難的問題


如果我們對上面的想法來改進一下呢,我們把四種基本面都做成各自的類,然後把配菜做成各自的類,最後在計算總價的時候將他們相加。


這種設計雖然對比與第一種設計思想,不會出現類災難的問題,但是當出現新的品類是還是需要修改主類中的部分功能比如:加和,所以還是不易維護的。

所以接下來就引入了我們的裝飾者模式的設計方案了。

先給出裝飾者的設計思路



基類,食物類


public abstract class Food {
	
	//描述屬性
	public String description="";
	//價格屬性
	private float price=0f;;
	
	
	//設定描述
	public void setDescription(String description)
	{
		this.description=description;
	}
	
	//獲取描述
	public String getDescription()
	{
		return description+"-"+this.getPrice();
	}
	
	//獲取價格
	public float getPrice()
	{
		return price;
	}
	
	//設定價格
	public void setPrice(float price)
	{
		this.price=price;
	}
	
	//價格計算
	public abstract float cost();
	
}


具體元件基類,麵條類


public  class Noodle extends Food {

	//計算價格
	@Override
	public float cost() {
		// TODO Auto-generated method stub
		return super.getPrice();
	}
}


四種基本面類:

細面類


public class ThinNoodle extends Noodle {
	public ThinNoodle()
	{
		super.setDescription("細面");
		super.setPrice(6.0f);
	}
}


寬面類


public class WideNoodle extends Noodle{
	
	public WideNoodle()
	{
		super.setDescription("寬面");
		super.setPrice(6.0f);
	}
}


刀削麵類


public class CutNoodle extends Noodle{
	
	public CutNoodle()
	{
		super.setDescription("刀削麵");
		super.setPrice(7.0f);
	}
}

貓耳面(原諒我可憐的英語吧。。)


public class  CatEarNoodle extends Noodle{
	
	public CatEarNoodle()
	{
		super.setDescription("貓耳面");
		super.setPrice(8.0f);
	}
}


裝飾者基類

裝飾者類


public  class Decorator extends Food {
	
	//食物類負責接受傳進來的基本面條類並進行裝飾
	private Food Obj;

	//建構函式
	public Decorator(Food Obj){
		this.Obj=Obj;
	};
	
	//價格計算
	@Override
	public float cost() {
		// TODO Auto-generated method stub	
		return super.getPrice()+Obj.cost();
	}
	
	//得到描述資訊
	@Override
	public String getDescription()
	{
		return super.description+"-"+super.getPrice()+"&&"+Obj.getDescription();
	}	
}


裝飾者具體類

青菜類


public class Vegetables extends Decorator {

	public Vegetables(Food Obj) {		
		super(Obj);
		// TODO Auto-generated constructor stub
		super.setDescription("青菜");
		super.setPrice(1.5f);
	}
}


雞蛋類


public class Egg extends Decorator {

	public Egg(Food Obj) {		
		super(Obj);
		// TODO Auto-generated constructor stub
		super.setDescription("雞蛋");
		super.setPrice(2.0f);
	}
}


牛肉類


public class Beef extends Decorator {

	public Beef(Food Obj) {		
		super(Obj);
		// TODO Auto-generated constructor stub
		super.setDescription("牛肉");
		super.setPrice(5.0f);
	}
}


由此我們可以看出,其實真正導致我們菜品價格變化的是裝飾者的搭配不同,而且在當我們的麵條基類生成好後,其實我們是對面條基類進行一層一層的裝飾,並且因為這樣的設計使得我們在計算價格的時候產生了一種類似於遞迴的計算方式,這樣非常利用我們的新裝飾者的擴充套件。


讓我們寫一個測試程式測試一下:


public class NoodleShop {
	public static void main(String[] args) {
		
		Food order;
		order=new ThinNoodle();
		System.out.println("一號訂單價格:"+order.cost());
		System.out.println("一號訂單描述:"+order.getDescription());
		
		System.out.println("#######################################");
		order=new CutNoodle();
		order=new Beef(order);
		order=new Egg(order);
		order=new Egg(order);
		System.out.println("二號訂單價格:"+order.cost());
		System.out.println("二號訂單描述:"+order.getDescription());
		
		System.out.println("#######################################");
		order=new CatEarNoodle();
		order=new Beef(order);
		order=new Egg(order);
		order=new Vegetables(order);
		System.out.println("三號訂單價格:"+order.cost());
		System.out.println("三號訂單描述:"+order.getDescription());	
	}
}




看來我們非常成功的完成了我們的麵店訂單收銀系統


那麼我們來看看裝飾者模式的優缺點和適用場景

優點:
    1.裝飾者模式可以提供比繼承更多的靈活性
    2.可以通過一種動態的方式來擴充套件一個物件的功能,在執行時選擇不同的裝飾器,從而實現不同的行為。
    3.通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行為的組合。可以使用多個具體裝飾類來裝飾同一物件,得到功能更為強大的物件。
    4.具體構件類與具體裝飾類可以獨立變化,使用者可以根據需要增加新的具體構件類和具體裝飾類,在使用時再對其進行組合,原有程式碼無須改變,符合“開-閉原則”。
缺點:
    1.會產生很多的小物件,增加了系統的複雜性
    2.這種比繼承更加靈活機動的特性,也同時意味著裝飾模式比繼承更加易於出錯排錯也很困難,對於多次裝飾的物件,除錯時尋找錯誤可能需要逐級排查,較為煩瑣。

適用場景:

    1. 需要擴充套件一個類的功能,或給一個類新增附加職責
    2. 需要動態的給一個物件新增功能,這些功能可以再動態的撤銷
    3. 需要增加由一些基本功能的排列組合而產生的非常大量的功能,從而使繼承關係變的不現實。

相關文章