目錄
-
- 星巴茲咖啡的故事
- 裝飾者模式
-
- 一、模式介紹
- 二、模式實作
- 三、模式設計
- 四、模式的優缺點
- 五、使用場景:
- 六、裝飾者用到的設計原則:
星巴茲咖啡的故事
- 現在有一個咖啡館,他有一套自己的訂單系統,當顧客來咖啡館的時候,可以通過訂單系統來點自己想要的咖啡。他們原先的設計是這樣子的:
- 每一種飲料都繼承抽象飲料類 Beverage,計算價錢隻需要調用相應子類的 cost() 方法即可,其中 description 是其相應的飲料描述。飲料少的情況下完全沒問題,但咖啡館為了吸引更多的顧客,在系統中允許顧客選擇加入不同的調料,如:蒸奶(Steamed Milk)、豆漿(Soy)、摩卡(Mocha,也就是巧克力風味)或覆寫奶泡。星巴茲會根據所加入的調料收取不同的費用。是以訂單系統必須考慮到這些調料部分。如果說依舊用原來的設計那麼整個系統會出現爆炸的狀況。(當然我隻畫了一小部分,再多點 visio 就崩潰了 /(ㄒoㄒ)/ )
- 這時,有個人提出了新的方案,利用執行個體變量和繼承,來追蹤這些調料。具體為:先從Beverage基類下手,加上執行個體變量代表是否加上調料(牛奶、豆漿、摩卡、奶泡……)
這種設計雖然滿足了現在的需求,但也存在非常大的局限性:
最主要的問題就是,如果顧客選擇了雙份的奶泡(配料),該怎麼辦?
其次,出現了新的材料,那麼還需要修改父類中的 cost() 方法,這違反了開放關閉原則(類應該對擴充開放,對修改關閉)。
…
那我們怎麼辦呢?好啦,裝飾者可以非常完美的解決以上的所有問題,讓我們有一個設計非常nice的咖啡館。
裝飾者模式
一、模式介紹
裝飾器模式(Decorator Pattern)允許向一個現有的對象添加新的功能,同時又不改變其結構。這種類型的設計模式屬于結構型模式,它是作為現有的類的一個包裝。
這種模式建立了一個裝飾類,用來包裝原有的類,并在保持類方法簽名完整性的前提下,提供了額外的功能。
我們通過下面的執行個體來示範裝飾器模式的用法。
二、模式實作
以摩卡和奶泡深焙咖啡為例:
步驟:
- 拿到一個深焙咖啡(DarkRoast)對象
- 以摩卡(Mocha)對象去裝飾它
- 以奶泡(Whip)對象去裝飾它
- 調用 cost() 方法
圖示步驟:
- 拿到一個深焙咖啡(DarkRoast)對象
- 顧客要摩卡(Mocha),是以建立 Mocha 對象,并用它将 DarkRoast 對象裝起來
- 顧客要奶泡(Whip),是以建立 Whip 對象,并用它将以 Mocha 裝飾的 DarkRoast 對象裝起來 可以把 Mocha 裝飾之後的 DarkRoast 仍然看成是一個 Beverage ,原因在于多态!無論是裝飾者摩卡(Mocha)、奶泡(Whip)還是被裝飾者深焙咖啡 歸根結底都是繼承着頂層的抽象類(Beverage)。是以無論 DarkRoast 被多少裝飾者裝飾着包裝着都是一個Beverage。
- 調用 cost() 方法,遞歸實作計算最終價錢。
三、模式設計
裝飾者模式的模闆類圖:
裝飾者模式所包含的角色如下:
- Component : 抽象構件角色,定義對象的接口,可以給這些對象動态增加職責。
- ConcreteComponent : 被裝飾者,它是我們将要動态地加上新行為的對象,它擴充自Component。
- Decorator : 這是裝飾者共同實作的接口(抽象類)。
- ConcreteDecator : 具體的裝飾者,可以加上新的方法。新行為使用過在舊行為前面或者後面做一些計算來添加的,并且每一個裝飾者 都有一個執行個體變量,它可以記錄所裝飾的事物。
根據以上模闆類圖來繪制出訂單系統的類圖:
代碼如下:
Beverage : 抽象飲料類
/**
* 抽象飲料類
*/
public abstract class Beverage {
String name = "Unknown Beverage";
//傳回飲料名字
public String getName() {
return name;
}
//cost方法是用來傳回飲料的價錢,需在具體類中自己實作
public abstract int cost();
}
具體飲料類:被裝飾者,包括:獨家調配的咖啡(HouseBlend)、深焙咖啡(DarkRoast)、濃縮咖啡(Espresso)、低咖啡因咖啡類(Decaf)
/**
* 獨家調配的咖啡類(一種具體飲料)
*/
public class HouseBlend extends Beverage {
//說明他是HouseBlend飲料
public HouseBlend() {
name = "獨家調配的咖啡";
}
//實作cost方法,用來傳回 獨家調配的咖啡 的價格
@Override
public int cost() {
return 9;
}
}
/**
* 深焙咖啡類(一種具體飲料)
*/
public class DarkRoast extends Beverage {
//說明他是DarkRoast飲料
public DarkRoast() {
name = "深焙咖啡";
}
//實作cost方法,用來傳回 深焙咖啡 的價格
@Override
public int cost() {
return 11;
}
}
/**
* 濃縮咖啡類(一種具體飲料)
*/
public class Espresso extends Beverage {
//說明他是Espresso飲料
public Espresso() {
name = "濃縮咖啡";
}
//實作cost方法,用來傳回 濃縮咖啡 的價格
@Override
public int cost() {
return 12;
}
}
/**
* 低咖啡因咖啡類(一種具體飲料)
*/
public class Decaf extends Beverage {
//說明他是Decaf飲料
public Decaf() {
name = "低咖啡因咖啡";
}
//實作cost方法,用來傳回 低咖啡因咖啡 的價格
@Override
public int cost() {
return 10;
}
}
Dector : 裝飾者共同實作的接口
/**
* 調料裝飾着抽象類(繼承自飲料抽象類)
*/
public abstract class Decorator extends Beverage {
/**
* 所有的調料裝飾者都必須重新實作getDescription()方法
* 這樣才能夠用遞歸的方式來得到所選飲料的整體描述
*/
public abstract String getName();
}
ConcreteDecorator : 具體的裝飾者,包括:牛奶(Milk)、摩卡(Mocha)、豆漿(Soy)、奶泡(Whip)
/**
* 牛奶調料類(繼承自 Decorator)
*/
public class Milk extends Decorator {
//用一個執行個體變量記錄飲料,也就是被裝飾者
Beverage beverage;
//構造器初始化飲料變量
public Milk(Beverage beverage) {
this.beverage = beverage;
}
//在原來飲料的基礎上添加上Milk描述(原來的飲料加入Milk調料,被Milk調料裝飾)
@Override
public String getName() {
return beverage.getName() + ",牛奶";
}
//在原來飲料的基礎上加上Soy的價格(原來的飲料加入Soy調料,被Soy調料裝飾)
@Override
public int cost() {
return beverage.cost() + 3;
}
}
/**
* 摩卡調料類(繼承自 Decorator)
*/
public class Mocha extends Decorator {
//用一個執行個體變量記錄飲料,也就是被裝飾者
Beverage beverage;
//構造器初始化飲料變量
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
//在原來飲料的基礎上添加上Mocha描述(原來的飲料加入Mocha調料,被Mocha調料裝飾)
@Override
public String getName() {
return beverage.getName() + ",摩卡";
}
//在原來飲料的基礎上加上Mocha的價格(原來的飲料加入Mocha調料,被Mocha調料裝飾)
@Override
public int cost() {
return beverage.cost() + 2;
}
}
/**
* 豆漿調料類(繼承自 Decorator)
*/
public class Soy extends Decorator {
//用一個執行個體變量記錄飲料,也就是被裝飾者
Beverage beverage;
//構造器初始化飲料變量
public Soy(Beverage beverage) {
this.beverage = beverage;
}
//在原來飲料的基礎上添加上Soy描述(原來的飲料加入Soy調料,被Soy調料裝飾)
@Override
public String getName() {
return beverage.getName() + ",豆漿";
}
//在原來飲料的基礎上加上Soy的價格(原來的飲料加入Soy調料,被Soy調料裝飾)
@Override
public int cost() {
return beverage.cost() + 1;
}
}
/**
* 奶泡調料類(繼承自 Decorator)
*/
public class Whip extends Decorator {
//用一個執行個體變量記錄飲料,也就是被裝飾者
Beverage beverage;
//構造器初始化飲料變量
public Whip(Beverage beverage) {
this.beverage = beverage;
}
//在原來飲料的基礎上添加上 Milk 描述(原來的飲料加入 Whip 調料,被 Whip 調料裝飾)
@Override
public String getName() {
return beverage.getName() + ",奶泡";
}
//在原來飲料的基礎上加上 Whip 的價格(原來的飲料加入Soy調料,被 Whip 調料裝飾)
@Override
public int cost() {
return beverage.cost() + 4;
}
}
Client : 用戶端示範代碼
public class Client {
public static void main(String[] args) {
System.out.println("飲料價格如下:");
System.out.println("獨家調配的咖啡類:9、低咖啡因咖啡:10、深焙咖啡類:11、濃縮咖啡類:12");
System.out.println("調料價格如下:");
System.out.println("豆漿:1、摩卡:2、牛奶:3、奶泡:4\n");
//訂一杯濃縮咖啡類Espresso(12) 對象,不需要調料,列印出它的描述與價錢。
Beverage beverage1 = new Espresso();
System.out.println("飲料描述: " + beverage1.getName() + " 價格為: " + beverage1.cost());
System.out.println("---------------------------------------------------");
//制造出一個深焙咖啡類DarkRoast(11) 對象,用摩卡Mocha(2)裝飾它,用第二個摩卡Mocha(2)裝飾它,用奶泡Whip(4)裝飾它,列印出它的描述與價錢。
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
System.out.println("飲料描述: " + beverage2.getName() + " 價格為: " + beverage2.cost());
System.out.println("---------------------------------------------------");
//也可用這種形式
Beverage beverage3 = new Mocha( new Mocha(new DarkRoast()));
System.out.println("飲料描述: " + beverage3.getName() + " 價格為: " + beverage3.cost());
System.out.println("---------------------------------------------------");
//再來一杯 低咖啡因咖啡Decaf(10)對象,并用豆漿Soy(1)、摩卡Mocha(2)、奶泡Whip(4),列印出它的描述與價錢。
Beverage beverage4 = new Decaf();
beverage4 = new Soy(beverage4);
beverage4 = new Mocha(beverage4);
beverage4 = new Whip(beverage4);
System.out.println("飲料描述: " + beverage4.getName() + " 價格為: " + beverage4.cost());
}
}
結果展示:
四、模式的優缺點
優點: 裝飾類和被裝飾類可以獨立發展,不會互相耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動态擴充一個實作類的功能。
缺點: 多層裝飾比較複雜。
五、使用場景:
- 擴充一個類的功能。
-
動态增加功能,動态撤銷。
六、裝飾者用到的設計原則:
- 多用組合,少用繼承。
- 對擴充開放,對修改關閉。
本文的主要思路以及開篇引述的故事都借鑒于下面這篇部落格,部落客寫的非常清晰,如果沒有看懂的地方,可前往連結檢視詳情。
參考部落格:https://www.cnblogs.com/of-fanruice/p/11565679.html