裝飾器模式又叫包裝模式,數據結構型模式;是指在不改變現有對象結構的情況下,動態的給改對象增加一些職責(即增加其額外功能)的模式。
在星巴克咖啡店,有美式咖啡(LongBlack)、無因咖啡(Decaf)、意大利農咖啡(Espresso)等不同的咖啡種類,也可以添加牛奶(Milk)、豆漿(Soy)、巧克力(Chocolate)等調料。下面我們就以這個為例子講解裝飾器模式。
使用傳統的方式
使用傳統的方式設計,就是每一種咖啡和每一種調料都寫一個類,都繼承自Drink抽象類。這樣的缺點是每增加一個單品咖啡,或者增加一個新的調料,類的數量就會成倍增加,形成類爆炸。
裝飾器模式
裝飾器模式的UML類圖:
從上圖可以看到裝飾器模式主要有抽象構件角色、具體構件角色、抽象裝飾角色、具體裝飾角色等四個角色:
- 抽象構件角色:給出一個抽象接口,以規范准備接收附加責任的對象
- 具體構件角色:實現抽象構件,並通過裝飾角色為其添加一些職責
- 抽象裝飾角色:繼承抽象構件角色,並包含具體構件的實例,可以通過其子類擴展具體構件的功能
- 具體裝飾角色:實現抽象裝飾的相關方法,並給具體構件對象添加附加的責任
使用裝飾器模式完成上面例子的UML類圖:
抽象構件角色:
package com.charon.decorator;
/**
* @className: Drink
* @description:
* @author: charon
* @create: 2022-03-19 22:48
*/
public abstract class Drink {
/**
* 描述
*/
public String desc;
/**
* 價格
*/
private float price = 0.0f;
/**
* Gets the value of desc
*
* @return the value of desc
*/
public String getDesc() {
return desc;
}
/**
* Sets the desc
*
* @param desc desc
*/
public void setDesc(String desc) {
this.desc = desc;
}
/**
* Gets the value of price
*
* @return the value of price
*/
public float getPrice() {
return price;
}
/**
* Sets the price
*
* @param price price
*/
public void setPrice(float price) {
this.price = price;
}
/**
* 計算費用的方法,交給子類實現
* @return
*/
public abstract float cost();
}
具體構建角色:
package com.charon.decorator;
/**
* @className: Coffee
* @description:
* @author: charon
* @create: 2022-03-19 22:37
*/
public class Coffee extends Drink{
@Override
public float cost() {
return super.getPrice();
}
}
package com.charon.decorator;
/**
* @className: LongBlack
* @description:
* @author: charon
* @create: 2022-03-19 23:16
*/
public class LongBlack extends Coffee{
public LongBlack() {
setDesc(" 美式咖啡 ");
setPrice(5.0f);
}
}
package com.charon.decorator;
/**
* @className: Decaf
* @description:
* @author: charon
* @create: 2022-03-19 23:11
*/
public class Decaf extends Coffee{
public Decaf() {
setDesc(" 無因咖啡 ");
setPrice(1.0f);
}
}
抽象裝飾角色:
package com.charon.decorator;
/**
* @className: Decorator
* @description:
* @author: charon
* @create: 2022-03-19 23:13
*/
public class Decorator extends Drink{
/**
* 使用聚合的方式
*/
private Drink drink;
public Decorator(Drink drink) {
this.drink = drink;
}
@Override
public float cost() {
return super.getPrice() + drink.cost();
}
@Override
public String getDesc() {
return super.getDesc() + "&&" + drink.getDesc();
}
}
具體裝飾角色:
package com.charon.decorator;
/**
* @className: Milk
* @description:
* @author: charon
* @create: 2022-03-19 23:17
*/
public class Milk extends Decorator{
public Milk(Drink drink) {
super(drink);
setDesc(" 牛奶 ");
setPrice(2.0f);
}
}
package com.charon.decorator;
/**
* @className: Soy
* @description:
* @author: charon
* @create: 2022-03-19 23:18
*/
public class Soy extends Decorator{
public Soy(Drink drink) {
super(drink);
setDesc(" 豆漿 ");
setPrice(1.5f);
}
}
測試:
package com.charon.decorator;
/**
* @className: Client
* @description:
* @author: charon
* @create: 2022-03-19 23:19
*/
public class Client {
public static void main(String[] args) {
// 點一份美式咖啡
Drink longBlack = new LongBlack();
System.out.println(longBlack.getDesc() + " 費用: " + longBlack.cost());
// 添加一點牛奶
longBlack = new Milk(longBlack);
System.out.println(" 添加了:" + longBlack.getDesc() + " 費用:" + longBlack.cost());
// 再添加一點豆漿
longBlack = new Soy(longBlack);
System.out.println(" 添加了:" + longBlack.getDesc() + " 費用:" + longBlack.cost());
}
}
打印:
美式咖啡 費用: 5.0
添加了: 牛奶 && 美式咖啡 費用:7.0
添加了: 豆漿 && 牛奶 && 美式咖啡 費用:8.5
裝飾器模式以對客戶端透明的方式擴展對象的功能,是繼承關系的一個替代方案。
裝飾器模式的優點:
- 裝飾器模式是對繼承的有利補充,比繼承靈活,在不改變原有對象的情況下,動態的給一個對象擴展功能,即插即用
- 通過使用不同裝飾類及這些裝飾類的排列組合,可以實現不同的效果
- 裝飾器模式完全遵循“開閉原則”
裝飾器模式的缺點:
- 裝飾器模式會增加許多子類,過度使用會增加程序的復雜性
裝飾器模式的應用場景
下面介紹其適用的應用場景,裝飾器模式通常在以下幾種情況使用。
- 當需要給一個現有類添加附加職責,而又不能采用生成子類的方法進行擴充時。例如,該類被隱藏或者該類是終極類或者采用繼承方式會產生大量的子類。
- 當需要通過對現有的一組基本功能進行排列組合而產生非常多的功能時,采用繼承關系很難實現,而采用裝飾器模式卻很好實現。
- 當對象的功能要求可以動態地添加,也可以再動態地撤銷。
裝飾器模式與橋接模式的區別
第一次看裝飾器模式,總感覺用橋接模式也能實現出來。但是其實兩者還是有區別的。
裝飾器模式的辦法就是把每個子類中比基類多出來的行為放到單獨的類里面。這樣當這些描述額外行為的對象被封裝到基類對象里面時,就得到了所需要的子類對象,將這些描述額外行為的類,排列組合可以造出很多的功能組合來,如果用靜態繼承的辦法創建這些組合出來的類所涉及到工作量很大,以致實際上很難做到。裝飾器模式要求所有的這些“額外行為類”具有相同的接口。
橋接模式的解決辦法則又有所不同,橋接模式把原來的兩個基類的實現化細節抽出來,再建造一個實現化的等級結構中,然后再把原有的基類改造成一個抽象化等級結構。橋接模式中抽象化角色的子類不能像裝飾器模式那樣嵌套,橋接模式卻可以連續使用。換言之,一個橋接模式的實現化角色可以成為下一步橋接模式的抽象化角色。
裝飾器模式與適配器模式的區別
裝飾器與適配器都有一個別名叫做 包裝模式(Wrapper),它們看似都是起到包裝一個類或對象的作用,但是使用它們的目的很不一樣。適配器模式的意義是要將一個接口轉變成另一個接口,它的目的是通過改變接口來達到重復使用的目的。
而裝飾器模式不是要改變被裝飾對象的接口,而是恰恰要保持原有的接口,但是增強原有對象的功能,或者改變原有對象的處理方式而提升性能。所以這兩個模式設計的目的是不同的。
在jdk中,InputStreamReader是一個適配器,因為它把InputStream的API轉換成Reader的API。InputStream是被適配的類,而 Reader是適配的目標類。InputStreamReader做為適配器類把InputStream類的一個實例包裝起來,從而能夠把InputStream的API。
而BufferReader是一個裝飾器類,因為它實現Reader,並且包裝了一個Reader。一些對流處理器可以對另一些流處理器起到裝飾作用,形成新的、具有改善功能得流處理器。類似地,BufferedInputStream、OutputStream、Writer 各自都是它們自己的裝飾類。LineNumberReader、FilterReader和 PushbackReader均是Reader的裝飾類,因為它們自己是Reader類,而且包裝其他的Reader類。CharArrayReader、FileReader、PipedReader和StringReader類不是裝飾類,因為它們包裝的是字符數值組、File、PipedWriter和String類。它們應該被看做字符數值組、File、PipedWriter 和String類的適配器類。