HTML DOM classList 屬性
裝飾器模式又叫包裝模式,資料結構型模式;是指在不改變現有物件結構的情況下,動態的給改物件增加一些職責(即增加其額外功能)的模式。
在星巴克咖啡店,有美式咖啡(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類的介面卡類。