設計模式——裝飾模式(Decorator)

要想正確理解設計模式,首先必須明確它是爲了解決什麼問題而提出來的。

設計模式學習筆記

——Shulin

轉載請註明出處:http://blog.csdn.net/zhshulin

1、概念

        裝飾模式又名包裝(Wrapper)模式。裝飾模式以對客戶端透明的方式擴展對象的功能,是繼承關係的一個替代方案

        但是純粹的裝飾模式很難找到,大多數的裝飾模式的實現都是“半透明”的,而不是完全透明的。換言之,允許裝飾模式改變接口,增加新的方法。半透明的裝飾模式是介於裝飾模式和適配器模式之間的。適配器模式的用意是改變所考慮的類的接口,也可以通過改寫一個或幾個方法,或增加新的方法來增強或改變所考慮的類的功能。

大多數的裝飾模式實際上是半透明的裝飾模式,這樣的裝飾模式也稱做半裝飾、半適配器模式。


2、針對的問題

        動態地給一個對象添加一些額外的職責。就增加功能來說,Decorator模式相比生成子類更爲靈活。不改變接口的前提下,增強所考慮的類的性能。

何時使用:

    1)需要擴展一個類的功能,或給一個類增加附加責任。

    2)需要動態的給一個對象增加功能,這些功能可以再動態地撤銷。

    3)需要增加一些基本功能的排列組合而產生的非常大量的功能,從而使繼承變得    不現實。


3、角色組成

    l 抽象構件(Component)角色:給出一個抽象接口,以規範準備接收附加責任的對象。

    l 具體構件(ConcreteComponent)角色:定義一個將要接收附加責任的類

    l 裝飾角色(Decorator):持有一個構件(Component)對象的實例,並定義一個與抽象構件接口一致的接口

    l 具體裝飾角色(ConcreteDecorator):負責給構件對象“貼上”附加的責任

4、舉例說明


        咖啡是一種飲料,咖啡的本質是咖啡豆+水磨出來的。咖啡店現在要賣各種口味的咖啡,如果不使用裝飾模式,那麼在銷售系統中,各種不一樣的咖啡都要產生一個類,如果有4中咖啡豆,5種口味,那麼將要產生至少20個類(不包括混合口味),非常麻煩。使用了裝飾模式,只需要11個類即可生產任意口味咖啡(包括混合口味)。


4.1、類圖



4.2、源碼

Beverage飲料接口

/**
 * 飲料接口
 * @author Administrator
 *
 */
public interface Beverage {
	//返回商品描述
	public String getDescription();
	//返回價格
	public double getPrice();
}

CoffeeBean1——具體被裝飾的對象類1

public class CoffeeBean1 implements Beverage {
	private String description = "選了第一種咖啡豆";
	@Override
	public String getDescription() {
		return description;
	}
	@Override
	public double getPrice() {
		return 50;
	}

}

CoffeeBean2——具體被裝飾的對象類2

public class CoffeeBean2 implements Beverage {
	private String description = "第二種咖啡豆!";
	@Override
	public String getDescription() {
		return description;
	}

	@Override
	public double getPrice() {
		return 100;
	}

}

Decorator——裝飾類

public class Decorator implements Beverage {
	private String description = "我只是裝飾器,不知道具體的描述";
	@Override
	public String getDescription() {
		return description;
	}
	@Override
	public double getPrice() {
		return 0;		//價格由子類來決定
	}

}

Milk——具體裝飾類,給咖啡加入牛奶

public class Milk extends Decorator{
	private String description = "加了牛奶!";
	private Beverage beverage = null;
	public Milk(Beverage beverage){
		this.beverage = beverage;
	}
	public String getDescription(){
		return beverage.getDescription()+"\n"+description;
	}
	public double getPrice(){
		return beverage.getPrice()+20;	//20表示牛奶的價格
	}
}

Mocha——給咖啡加入摩卡

public class Mocha extends Decorator {
	private String description = "加了摩卡!";
	private Beverage beverage = null;
	public Mocha(Beverage beverage){
		this.beverage = beverage;
	}
	public String getDescription(){
		return beverage.getDescription()+"\n"+description;
	}
	public double getPrice(){
		return beverage.getPrice()+49;	//30表示摩卡的價格
	}
}

Soy——給咖啡加入豆漿

public class Soy extends Decorator {
	private String description = "加了豆漿!";
	private Beverage beverage = null;
	public Soy(Beverage beverage){
		this.beverage = beverage;
	}
	public String getDescription(){
		return beverage.getDescription()+"\n"+description;
	}
	public double getPrice(){
		return beverage.getPrice()+30;	//30表示豆漿的價格
	}
}

測試類:

public class Test {

	public static void main(String[] args) {
		Beverage beverage = new CoffeeBean1();	//選擇了第一種咖啡豆磨製的咖啡
		beverage = new Mocha(beverage);		//爲咖啡加了摩卡
		beverage = new Milk(beverage);
		System.out.println(beverage.getDescription()+"\n加了摩卡和牛奶的咖啡價格:"+beverage.getPrice());
		
	}
}

測試結果:


5、裝飾模式和適配器模式的關係

        裝飾模式和適配器模式都是“包裝模式(Wrapper Pattern)”,它們都是通過封裝其他對象達到設計的目的的,但是它們的形態有很大區別。

 

  理想的裝飾模式在對被裝飾對象進行功能增強的同時,要求具體構件角色、裝飾角色的接口與抽象構件角色的接口完全一致。而適配器模式則不然,一般而言,適配器模式並不要求對源對象的功能進行增強,但是會改變源對象的接口,以便和目標接口相符合。

 

  裝飾模式有透明和半透明兩種,這兩種的區別就在於裝飾角色的接口與抽象構件角色的接口是否完全一致。透明的裝飾模式也就是理想的裝飾模式,要求具體構件角色、裝飾角色的接口與抽象構件角色的接口完全一致。相反,如果裝飾角色的接口與抽象構件角色接口不一致,也就是說裝飾角色的接口比抽象構件角色的接口寬的話,裝飾角色實際上已經成了一個適配器角色,這種裝飾模式也是可以接受的,稱爲“半透明”的裝飾模式,如下圖所示。



 在適配器模式裏面,適配器類的接口通常會與目標類的接口重疊,但往往並不完全相同。換言之,適配器類的接口會比被裝飾的目標類接口寬。

顯然,半透明的裝飾模式實際上就是處於適配器模式與裝飾模式之間的灰色地帶。如果將裝飾模式與適配器模式合併成爲一個“包裝模式”的話,那麼半透明的裝飾模式倒可以成爲這種合併後的“包裝模式”的代表。


6、透明性要求

        裝飾模式對客戶端的透明性要求程序不要聲明一個ConcreteComponent類型的變量,而應當聲明一個Component類型的變量。

  用上面的例子來說,必須永遠把所有的飲料當成飲料來對待,而如果把飲料變成的加摩卡的飲料當成摩卡,而不是飲料,這是不應當發生的。下面的做法是對的:

Beverage beverage = new CoffeeBean1();
Beverage mochaBeverage = new Mocha(beverage);

而下面的做法是不對的:

Beverage beverage = new CoffeeBean1();
Mocha mochaBeverage = new Mocha(beverage);

7、半透明模式

        然而,純粹的裝飾模式很難找到。裝飾模式的用意是在不改變接口的前提下,增強所考慮的類的性能。在增強性能的時候,往往需要建立新的公開的方法。即便是在孫大聖的系統(如下圖)裏,也需要新的方法。比如齊天大聖類並沒有飛行的能力,而鳥兒有。這就意味着鳥兒應當有一個新的fly()方法。再比如,齊天大聖類並沒有游泳的能力,而魚兒有,這就意味着在魚兒類裏應當有一個新的swim()方法。

        這就導致了大多數的裝飾模式的實現都是“半透明”的,而不是完全透明的。換言之,允許裝飾模式改變接口,增加新的方法。這意味着客戶端可以聲明ConcreteDecorator類型的變量,從而可以調用ConcreteDecorator類中才有的方法:

TheGreatestSage sage = new Monkey();
Bird bird = new Bird(sage);
bird.fly();

        半透明的裝飾模式是介於裝飾模式和適配器模式之間的。適配器模式的用意是改變所考慮的類的接口,也可以通過改寫一個或幾個方法,或增加新的方法來增強或改變所考慮的類的功能。大多數的裝飾模式實際上是半透明的裝飾模式,這樣的裝飾模式也稱做半裝飾、半適配器模式。


8、優點

        (1)裝飾模式與繼承關係的目的都是要擴展對象的功能,但是裝飾模式可以提供比繼承更多的靈活性。裝飾模式允許系統動態決定“貼上”一個需要的“裝飾”,或者除          掉一個不需要的“裝飾”。繼承關係則不同,繼承關係是靜態的,它在系統運行前就決定了。

  (2)通過使用不同的具體裝飾類以及這些裝飾類的排列組合,設計師可以創造出很多不同行爲的組合。

9、缺點

        使用裝飾模式會產生比使用繼承關係更多的對象。更多的對象會使得查錯變得困難,特別是這些對象看上去都很相像。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章