天天看點

Java設計模式——裝飾者模式【Decorator Pattern】

一、引言

23種設計模式大概分為三大類:

5種(建立型模式):工廠方法模式、抽象工廠模式、單例模式、原型模式、建造者模式。

7種(結構型模式):擴充卡模式、橋接模式、代理模式、外觀模式、裝飾者模式、組合模式、享元模式。

11種(行為型模式):政策模式、模闆方法模式、觀察者模式、疊代器模式、責任鍊模式、指令模式、備忘錄模式、狀态模式、通路者模式、中介者模式、解釋器模式。

行為型又可以通過類與類之間的關系進行劃分 :

Java設計模式——裝飾者模式【Decorator Pattern】

裝飾者模式基本介紹:

  • 裝飾者模式(Decorator):動态地将新功能附加到對象上,在對象功能擴充方面,它比繼承更有彈性,裝飾者模式也展現了開閉原則(ocp)

二、裝飾者模式

1.裝飾者模式原理類圖

Java設計模式——裝飾者模式【Decorator Pattern】
  • 裝飾者模式就像打包一個快遞

    主體:比如:陶瓷、衣服 (Component) //被裝飾者

    包裝:比如:報紙填充、塑膠泡沫、紙闆、木闆(Decorator) //裝飾者

  • Component: 主體
  • ConcreteComponent 和 Decorator
    • ConcreteComponent:具體的主體
    • Decorator:裝飾者,比如各調料,裝飾者裡聚合了一個Component,也就是說,ConcreteComponent是可以放到裝飾者裡面的(裝飾者裡面可以包含被裝飾者)
  • 在如圖的 Component 與 ConcreteComponent 之間,如果 ConcreteComponent 類很多,還可以設計一個緩沖層,将共有的部分提取出來,抽象成一個類。

三、具體需求

1.星巴克咖啡

  • 星巴克咖啡館
  • 咖啡種類/單品咖啡:Espresso(意大利濃咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(無因咖啡)
  • 調料:Milk、Soy(豆漿)、Chocolate
要求在擴充新的咖啡種類時,具有良好的擴充性、改動友善、維護友善。

要求計算不同種類咖啡的費用: 客戶可以點單品咖啡,也可以單品咖啡+調料組合。

2. 傳統方案

思路一分析 - 類圖

Java設計模式——裝飾者模式【Decorator Pattern】

類圖分析:

  • Drink 是一個抽象類,表示飲料
    • description 就是對咖啡的描述,比如咖啡的名字
    • cost()方法就是計算費用,Drink 類中做成一個抽象方法
  • Decaf 就是單品咖啡,繼承 Drink,并實作了 cost()
  • Espress && Milk 就是單品咖啡+調料, 這個組合很多
方案一問題分析:這樣設計會有很多類,當需要增加一個單品咖啡,或者一個新的調料時,類的數量就會倍增,就會出現類爆炸

思路二分析

前面思路一因為咖啡單品+調料組合會造成類的倍增,是以可以做改進,将調料内置到 Drink 類,這樣就不會造成類數量過多,進而提高項目的維護性

Java設計模式——裝飾者模式【Decorator Pattern】

說明:milk,soy,chocolate 可以設計為 Boolean,表示是否要添加相應的調料

方案二問題分析:
  • 方案 2 可以控制類的數量,不至于造成很多的類
  • 在增加或者删除調料種類時,代碼的維護量很大
  • 考慮到使用者可以添加多份調料,可以将 hasMilk 傳回一個對應 int
  • 改進:考慮使用 裝飾者 模式

3. 裝飾者模式方案

使用裝飾者模式改進傳統方式,讓程式具有更好的擴充性

思路分析 - 類圖

Java設計模式——裝飾者模式【Decorator Pattern】

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

Java設計模式——裝飾者模式【Decorator Pattern】

說明:

  • Milk包含了LongBlack
  • 一份Chocolate包含了(Milk+LongBlack)
  • 一份Chocolate包含了(Chocolate+Milk+LongBlack)
  • 這樣,不管是什麼形式的單品咖啡+調料組合,通過遞歸方式都可以友善地組合和維護

具體實作

// 接口 Drink.java
public abstract class Drink {
	public String des;//描述
	private float price = 0.0f;
	// get / set方法略
	
	//計算費用的抽象方法:由子類實作
	public abstract float cost();
}

// Coffee.java
public class Coffee extends Drink{
	@Override
	public float cost() {
		return super.getPrice();
	}
}

// Espresso.java/LongBlack.java/ShortBlack.java
public class Espresso extends Coffee{
	public Espresso() {
		setDes("意大利咖啡");
		setPrice(6.0f);
	}
}

// Decorator.java  繼承Drink又組合Drink
public class Decorator extends Drink{
	private Drink drink;
	
	public Decorator(Drink drink) {
		this.drink = drink;
	}
    
	@Override
	public float cost() {
		//自己(調料)的價格+咖啡的價格
		return super.getPrice() + drink.getPrice();
	}
    
	@Override
	public String getDes() {
		//父類的資訊+被裝飾者的資訊
		return super.des+" "+super.getPrice()+" && "+drink.getDes();
	}
}
// Chocolate.java/Milk.java/Soy.java
//具體的Decorator,這裡就是調味品
public class Chocolate extends Decorator {
	public Chocolate(Drink drink) {//必須有構造器
		super(drink);
		setDes("巧克力");
		setPrice(3.5f);
	}
}
// CoffeeBar.java 使用
public class CoffeeBar {
	public static void main(String[] args) {
		//用裝飾者模式下單:2份Chocolate+1份milk+LongBlack
		//1.先點一份LongBlack
		Drink coffee = new LongBlack();
		System.out.println("單品:"+coffee.getDes()+" 價格:"+coffee.cost());
		//2.加入一份牛奶
		coffee = new Milk(coffee);
		System.out.println("加入一份牛奶後:"+coffee.getDes()+" 價格:"+coffee.cost());
		
		//3.加入一份巧克力
		coffee = new Chocolate(coffee);
		System.out.println("加入一份牛奶+巧克力後:"+coffee.getDes()+" 價格:"+coffee.cost());
		
		//4.再加入一份巧克力
		coffee = new Chocolate(coffee);
		System.out.println("加入一份牛奶+2份巧克力後:"+coffee.getDes()+" 價格:"+coffee.cost());
	}
}