天天看點

設計模式:裝飾者模式緣由書中例子引入裝飾者模式代碼java的IO類實作了裝飾者模式總結源代碼

緣由

沒什麼特别的,之前看懂了,這次自己再複述一下。畢竟把别人講懂了才是真的懂了。主要參考了head first 設計模式。

書中例子

面和具體的面

例子講述的是在為星巴克咖啡的制作訂單的情況,比如客人點了飲料,那麼系統會自動算出價格(不知道是我沒有體會到,還是這個例子不太合适,算出價格那麼簡單的事還需要用到類?,不過不影響我們思考裝飾者模式)。不過似乎星巴克離普通的中國人還是太遙遠了,我倒可以認為我們想象成中國面館比較好。

這家中國面館賣很多種類的面,有我喜歡的哨子面、牛肉面、雜醬面、闆面等等面,每一個面都有價格,這是非常正常的。如果每一種面都有一個價格,那麼這個訂單生成系統好像還比較簡單。因為繼承是一種不錯的選擇。下圖直接截取了head first 設計模式,是以裡面是飲料。那麼飲料就相當于我上面所述的面,而牛肉面就相當于一個飲料的一種,比如:Dark Roast。

設計模式:裝飾者模式緣由書中例子引入裝飾者模式代碼java的IO類實作了裝飾者模式總結源代碼

加了配料的面

問題來了,面還可以加各類配料,比如你說老闆多加一個雞蛋,另一個說多加一個肉丸,多加一個火腿腸。難道我們為每一種加了配料的面都來實作一種類來計算價錢嗎?如此一來,将會有非常多的類。比如加了雞蛋的牛肉面、加了肉丸的牛肉面.....那麼我們的類就繼承就會成為下面這個樣子。

設計模式:裝飾者模式緣由書中例子引入裝飾者模式代碼java的IO類實作了裝飾者模式總結源代碼

可以看出我們要實作每一個子類就非常辛苦了,需要自己懂把加了丸子、雞蛋的牛肉面,提前加一遍算好放在這個子類裡使用。更可怕的是:如果丸子價格漲了,怎麼辦?我們又要把所有的有丸子的子類都加一遍嗎?

将調料試着執行個體變量

這是一種可以解決的方案。在父類面裡面有丸子、雞蛋的布爾值,和具體價格,那麼使用者在選擇添加了雞蛋的時候,就将雞蛋的布爾值改為true,然後再将父類的cost()方法實作為加上所有的配料的價格。那麼子類在cost()方法裡面用自己這道面的價格加上父類的cost(),就是配料的價格,那麼就算出了總共需要多少錢了。

設計模式:裝飾者模式緣由書中例子引入裝飾者模式代碼java的IO類實作了裝飾者模式總結源代碼
設計模式:裝飾者模式緣由書中例子引入裝飾者模式代碼java的IO類實作了裝飾者模式總結源代碼

下圖是僞代碼,我們可以看出,子類DarkRoast的cost()方面裡面調用了父類的cost()方法,而父類cost()方面,主要是判斷這個訂單有沒有加這個某種配料,加了的話就加上這個配料的價格。

設計模式:裝飾者模式緣由書中例子引入裝飾者模式代碼java的IO類實作了裝飾者模式總結源代碼

然而,這樣做有以下三種情況會使我們很難處理:

  1. 如果出現新調料,我們需要改變超類。似乎這樣很不好
  2. 某些飲料裡面就不能加某些調料,然而我們并沒有限制
  3. 如果某個客戶想要雙倍的調料,則無法處理。

這裡書中: 提出了類應該對擴充開放,對修改關閉。

因為

  • 對類修改也許會引入額外的bug,那麼上面的設計也許會使得我們修改超類,這樣非常不好。
  • 而如果我們有需要,可以通過對類進行擴充來完成。

引入裝飾者模式

此時書中引入裝飾者模式,其實我是知道看來了代碼之後才知道到底是怎麼回事。我的了解就是,所謂裝飾者,其也是繼承于基類(面)的一個實作類,這個實作類必須有有有一個成員變量:就是另一個實作類,也就是被包含的實作類,也就是我們裝飾者所要裝飾的對象。比如牛肉面就是一個實作類,他是被裝飾的,可以被加雞蛋這個實作類來修飾,加雞蛋這個實作類的成員變量可以引用牛肉面,那麼就組成了加雞蛋的牛肉面,如果其成員變量引用了哨子面,那麼就成了加雞蛋的哨子面。而且,這個加雞蛋的哨子面還可以被加丸子的實作類來裝飾,那麼就是成了加丸子 加雞蛋的牛肉面。當然,也可以加兩個雞蛋。

是以,我們要把實作類分為兩類,一種實作類時調料,其有一個成員變量就是基類的一個對象。另一個實作類就是面的種類:牛肉面、哨子面。

我在書上的圖,作了一些額外的說明。如下:

設計模式:裝飾者模式緣由書中例子引入裝飾者模式代碼java的IO類實作了裝飾者模式總結源代碼

另外書上也給了飲料的圖,更為簡單和清晰些:

設計模式:裝飾者模式緣由書中例子引入裝飾者模式代碼java的IO類實作了裝飾者模式總結源代碼

代碼

實際上我還是覺得代碼描述的更為清楚:

設計模式:裝飾者模式緣由書中例子引入裝飾者模式代碼java的IO類實作了裝飾者模式總結源代碼
public class TestMain {
	public static void main(String[] args) {
		//普通牛肉面
		Noodle beefNoodle = new BeefNoodle();
		System.out.println(beefNoodle.getDescription() + ":" + beefNoodle.cost());
		//普通豬肉面
		Noodle porkNoodle = new PorkNoodle();
		System.out.println(porkNoodle.getDescription() + ":" + porkNoodle.cost());
		//加了雞蛋的牛肉面
		Noodle beefNoodle2 = new BeefNoodle();
		beefNoodle2 = new EggCondiment(beefNoodle2);
		System.out.println(beefNoodle2.getDescription() + ":" + beefNoodle2.cost());
		//加了雞蛋和丸子的豬肉面
		Noodle porkNoodle2 = new PorkNoodle();
		porkNoodle2 = new EggCondiment(porkNoodle2);
		porkNoodle2 = new MeatballCondiment(porkNoodle2);
		System.out.println(porkNoodle2.getDescription() + ":" + porkNoodle2.cost());
	}
}

           
/**
 * 這是一個面的基類。是以面的種類的實作類和調料的裝飾類都必須繼承這個類。
 * @author zy
 *
 */
public abstract class Noodle {
	String description = "unknown noodle";
	public String getDescription(){
		return description;
	}
	public abstract double cost();
	
}
           
public class BeefNoodle extends Noodle {

	public BeefNoodle() {
		// TODO Auto-generated constructor stub
		description = "牛肉面";
	}
	@Override
	public double cost() {
		return 10;//表示這個碗牛肉面10元,當然可以做的更專業一些,用個字段來設定。
	}

}
           
public class PorkNoodle extends Noodle {

	public PorkNoodle() {
		// TODO Auto-generated constructor stub
		description = "豬肉面";
	}
	@Override
	public double cost() {
		// TODO Auto-generated method stub
		return 7;
	}

}
           
public abstract class CondimenDecorator extends Noodle {
	/**
	 * 用于裝飾的實作類必須有一個成員變量就是要裝飾的對象。在這裡就是面
	 * 使用Noodle這個基類的原因除了一般的表示種類的面
	 * 用于裝飾的實作類,還可以再次被裝飾
	 * 有點像加雞蛋、加丸子的牛肉面
	 * 被裝飾了兩次
	 * 
	 * 此外,書上講這個成員變量寫在了每一個實作類裡面,難道不能寫在這個父類裡面麼?
	 * 試試看。
	 */
	Noodle noodle;
	/**
	 * 是以調料必須實作這個方法,這樣才能知道這個面的具體描述
	 * 比如 加雞蛋的牛肉面
	 */
	public abstract String getDescription();
}
           
public class EggCondiment extends CondimenDecorator {
	public EggCondiment(Noodle noodle) {
		// TODO Auto-generated constructor stub
		this.noodle = noodle;
	}
	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return noodle.getDescription() + "加雞蛋";
	}

	@Override
	public double cost() {
		// TODO Auto-generated method stub
		return noodle.cost() + 1;
	}

}
           
public class MeatballCondiment extends CondimenDecorator {
	public MeatballCondiment(Noodle noodle) {
		// TODO Auto-generated constructor stub
		this.noodle = noodle;
	
	}

	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return noodle.getDescription() + "加肉丸";
	}

	@Override
	public double cost() {
		// TODO Auto-generated method stub
		return noodle.cost() + 2;
	}

}
           

運作結果

牛肉面:10.0
豬肉面:7.0
牛肉面加雞蛋:11.0
豬肉面加雞蛋加肉丸:10.0
           

java的IO類實作了裝飾者模式

如下圖所示:

設計模式:裝飾者模式緣由書中例子引入裝飾者模式代碼java的IO類實作了裝飾者模式總結源代碼

其中我們可以看出FilterInputStream專門作為裝飾者。其實作類LineNumberInputStream就可以得到InputStream流中的行數。

書中還舉出了簡單的例子,自己寫了一個FilterInputStream的實作類。也就是裝飾類。有新興趣的看看書吧

總結

比較有趣,不過這些設計模式都需要深刻的體會。懂了其含義隻是剛剛開始吧。對了,定義很難了解。是以,一直沒說。

設計模式:裝飾者模式緣由書中例子引入裝飾者模式代碼java的IO類實作了裝飾者模式總結源代碼

源代碼

沒有上傳網盤。代碼比較簡單。