10. 設計模式-裝飾者模式

設計模式-裝飾者模式

1. 案例引出裝飾者模式

星巴克咖啡訂單項目

  1. 咖啡種類:Espresso(意大利濃咖啡)、LongBlack(美式咖啡)、Decaf(無因咖啡)
  2. 調料:Milk、Chocolate。
  3. 要求在擴展新的咖啡種類時,具有良好的擴展性、改動方便、維護方便
  4. 使用 OO 的來計算不同種類咖啡的費用: 客戶可以點單品咖啡,也可以單品咖啡+調料組合。
  5. 傳統實現方式如下:

在這裏插入圖片描述

傳統實現方式分析:

  1. Drink 是一個抽象類,表示抽象飲料。
  2. description 就是對咖啡的描述, 比如咖啡的名字、受歡迎程度等
  3. cost() 方法就是計算費用,Drink 類中做成一個抽象方法.
  4. Decaf 就是單品咖啡, 繼承 Drink, 並實重寫 cost方法。
  5. Espress && Milk 就是單品咖啡+調料, 這個組合很多
  6. 問題: 這樣設計會有很多類,當我們增加一個單品咖啡 , 或者一個新的調料 , 類的數量就會倍,就會出現類爆炸

改進傳統方案:

  1. 前面分析到傳統方案 因爲咖啡單品+調料組合會造成類的倍增,因此可以做改進,將調料內置到 Drink 類,這樣就不會造成類數量過多。從而提高項目的維護性:
  2. 說明: milk,chocolate 可以設計爲 Boolean,表示是否要添加相應的調料。
  3. 在這裏插入圖片描述
  4. 改進後解決星巴克咖啡訂單問題的方案分析
    1. 改進後可以控制類的數量,不至於造成很多的類
    2. 在增加或者刪除調料種類時,代碼的維護量很大
    3. 考慮到用戶可以添加多份 調料時,可以將 hasMilk 返回一個對應 int
    4. 下面我們看一下設計模式中的裝飾模式,見識一下它的力量…

2. 裝飾者模式

2.1 裝飾者模式定義
  1. 裝飾者模式: 動態的將新功能附加到對象上。在對象功能擴展方面,它比繼承更有彈性, 裝飾者模式也體現了開閉原則(ocp)。
  2. 這裏提到的動態的將新功能附加到對象和 ocp 原則,在後面的應用實例上會以代碼的形式體現。
2.2 裝飾者模式原理

裝飾者模式就像打包一個快遞

  • 主體:比如:陶瓷、衣服,是被裝飾者 Component

  • 包裝:比如:報紙填充、塑料泡沫、紙板、木板,是裝飾着Decorator

在這裏插入圖片描述

  1. Component 主體:比如類似前面的 Drink
  2. ConcreteComponent 和 Decorator
    1. ConcreteComponent:具體的主體,比如前面的各個單品咖啡
    2. Decorator: 裝飾者,比如各調料.
  3. 在如圖的 Component 與 ConcreteComponent 之間,如果 ConcreteComponent 類很多,還可以設計一個緩衝層,將共有的部分提取出來,抽象層一個類。
2.3 裝飾者模式解決星巴克咖啡訂單問題

在這裏插入圖片描述

2.4 代碼實現

結合着上圖來看:

抽象類Drink以及其子類
// 抽象類Drink
public abstract class Drink {
	// 描述
	public String des; 
    // 價格
	private float price = 0.0f;
    
	public String getDes() {
		return des;
	}
	public void setDes(String des) {
		this.des = des;
	}
	public float getPrice() {
		return price;
	}
	public void setPrice(float price) {
		this.price = price;
	}
	
	//計算費用的抽象方法
	//子類來實現
	public abstract float cost();	
}

// 緩衝層Coffee
public class Coffee  extends Drink {

	@Override
	public float cost() {
		return super.getPrice();
	}
}

// 具體的單品咖啡ShortBlack
public class ShortBlack extends Coffee{
	
	public ShortBlack() {
		setDes(" shortblack ");
		setPrice(4.0f);
	}
}

// 具體的單品咖啡LongBlack
public class LongBlack extends Coffee {

	public LongBlack() {
		setDes(" longblack ");
		setPrice(5.0f);
	}
}
裝飾者Decorator着及其子類☆

關鍵體現在Decorator這個類中:

  1. Decorator繼承Drink類
  2. Decorator聚合Drink類
  3. Decorator組合Drink類
// 裝飾者
public class Decorator extends Drink {
    // 聚合
	private Drink obj;
	
    // 組合
	public Decorator(Drink obj) { 
		this.obj = obj;
	}
	
	@Override
	public float cost() {
		// getPrice 自己價格
		return super.getPrice() + obj.cost();
	}
	
	@Override
	public String getDes() {
		// obj.getDes() 輸出被裝飾者的信息
		return des + " " + getPrice() + " && " + obj.getDes();
	}
}


//具體的Decorator, 這裏就是調味品Chocolate
public class Chocolate extends Decorator {

	public Chocolate(Drink obj) {
		super(obj);
		setDes(" 巧克力 ");
		setPrice(3.0f); // 調味品 的價格
	}

}

//具體的Decorator, 這裏就是調味品Milk
public class Milk extends Decorator {
	public Milk(Drink obj) {
		super(obj);
		// TODO Auto-generated constructor stub
		setDes(" 牛奶 ");
		setPrice(2.0f); 
	}
}
測試星巴克咖啡下單

裝飾者模式下的訂單:2份巧克力+ 1份牛奶的LongBlack

public class CoffeeBar {

	public static void main(String[] args) {
		// 裝飾者模式下的訂單:2份巧克力+一份牛奶的LongBlack

		// 1. 單點一份 LongBlack
		Drink order = new LongBlack();
		System.out.println("費用1=" + order.cost());
		System.out.println("描述=" + order.getDes());

		// 2. 訂單 order 加入一份牛奶
		order = new Milk(order);

		System.out.println("order 加入一份牛奶 費用 =" + order.cost());
		System.out.println("order 加入一份牛奶 描述 = " + order.getDes());

		// 3. 訂單 order 加入一份巧克力

		order = new Chocolate(order);

		System.out.println("order 加入一份牛奶 加入一份巧克力  費用 =" + order.cost());
		System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDes());
	}
}

3. 裝飾者模式在 JDK IO源碼體現分析

Java 的 IO 結構,FilterInputStream 就是一個裝飾者,源碼如下,通過源碼我們發現FilterInputStream與我們之前的Decorator有以下相同特徵:

  1. FilterInputStream裝飾者繼承InputStream
  2. FilterInputStream裝飾者聚合InputStream
  3. FilterInputStream裝飾者組合InputStream
public class FilterInputStream extends InputStream {
    /**
     * 要過濾的輸入流。這裏使用聚合
     */
    protected volatile InputStream in;

    /**
     * 通過將參數賦值給字段this來創建FilterInputStream。以便記住以後使用。
     * 
     */
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
    // ...
}

下面是JDK IO源碼InputStram中的源碼的UML類圖,你只要瞭解FilterInputStream類與抽象類InputStream之間的關係(繼承,組合,聚合),然後就很容易理解了。

在這裏插入圖片描述

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