一 引言
不管是學生,還是上班族,好多人壓點起床出門總是常事,樓下小攤小店急匆匆的扒拉幾個包子一杯豆漿就衝向了地鐵或者公車,就這杯小小的豆漿,講究那可就大了,什麼大豆豆漿,五穀豆漿,黑芝麻豆漿種類繁多,要是還想加點配料那可更是花樣百出,喜歡甜的加點蜂蜜,喜歡吃棗的還能加幾粒紅棗,竟可能的滿足你的需要
其實從這一個豆漿的例子就能看出來,豆漿(好幾種,大豆、五穀等等)本身是一個現有的產品,而新增蜂蜜也好,紅棗也好,都是屬於在現有產品上增加新的功能或者美化,這就是我們今天要講的裝飾者模式
進一步說到技術層次上,有時候一些元件核心程式碼是固定的,但是想不改變原有結構的基礎上,進行一定的動態擴充套件,也是用如此的方式
下面接著用一個豆漿例子的具體程式碼引入其寫法,後面再對其理論進行闡述
二 程式碼演示
既然是豆漿,那就屬於飲品,首先定義一個抽象層面的飲品類
/**
* 抽象飲品類,被豆漿等子類繼承
*/
public abstract class Drink {
private String name; //飲品名稱
private float price = 0.0f;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
// 計算費用
public abstract float calcCost();
}
既然有了抽象的飲品,那麼豆漿類就可以出現了,別忘了實現父類中計算飲品費用的抽象方法
/**
* 飲品子類:豆漿類
*/
public class SoyaBeanMilk extends Drink {
@Override
public float calcCost() {
return super.getPrice();
}
}
而我們有各種豆漿,例如黃豆豆漿,五穀豆漿,黑芝麻豆漿
/**
* 豆漿子類:黃豆豆漿
*/
public class SoybeansSoyaBeanMilk extends SoyaBeanMilk {
public SoybeansSoyaBeanMilk() {
setName("黃豆豆漿");
setPrice(3.0f);
}
}
/**
* 豆漿子類:五穀豆漿
*/
public class GrainSoyaBeanMilk extends SoyaBeanMilk {
public GrainSoyaBeanMilk() {
setName("五穀豆漿");
setPrice(5.0f);
}
}
/**
* 豆漿子類:黑芝麻豆漿
*/
public class BlackSesameSoyaBeanMilk extends SoyaBeanMilk {
public BlackSesameSoyaBeanMilk() {
setName("黑芝麻豆漿");
setPrice(6.0f);
}
}
其實現在,我們已經可以拿到幾種口味的豆漿了,但是為了滿足需要,我們還需要增加一些因人而異的配料,例如蜂蜜和紅棗
首先,建立一個裝飾類 Decorator ,其通俗意義就是我們對於所有配料的一個總稱,下面會有一些具體的配料子類來繼承它
注:這個地方為了重寫描述和費用計算的方法,所以要繼承 Drink,注意分清楚哪些呼叫的是父類的,哪些是自己的
/**
* 裝飾類
*/
public class Decorator extends Drink {
private Drink drink;
public Decorator(Drink drink) {
this.drink = drink;
}
@Override
public String getName() {
return drink.getName() + " + " + super.getName() + "(單價" + getPrice() + ")";
}
@Override
public float calcCost() {
return super.getPrice() + drink.calcCost();
}
}
下面就是具體的配料子類了
/**
* 配料子類:蜂蜜配料
*/
public class Honey extends Decorator{
public Honey(Drink drink) {
super(drink);
setName("蜂蜜");
setPrice(2.0f);
}
}
/**
* 配料子類:紅棗配料
*/
public class RedJujube extends Decorator{
public RedJujube(Drink drink) {
super(drink);
setName("紅棗");
setPrice(3.0f);
}
}
測試一下
public class Test {
public static void main(String[] args) {
// 點1杯大豆豆漿
Drink drink = new SoybeansSoyaBeanMilk();
System.out.println("購買:" + drink.getName() + " --- 費用: " + drink.calcCost());
// 點1杯大豆豆漿 + 1份蜂蜜
drink = new Honey(drink);
System.out.println("購買:" + drink.getName() + " --- 費用: " + drink.calcCost());
// 點1杯大豆豆漿 + 1份蜂蜜 + 1份紅棗
drink = new RedJujube(drink);
System.out.println("購買:" + drink.getName() + " --- 費用: " + drink.calcCost());
// 點1杯大豆豆漿 + 1份蜂蜜 + 2份紅棗
drink = new RedJujube(drink);
System.out.println("購買:" + drink.getName() + " --- 費用: " + drink.calcCost());
}
}
執行結果:
購買:黃豆豆漿 --- 費用: 3.0
購買:黃豆豆漿 + 蜂蜜(單價2.0) --- 費用: 5.0
購買:黃豆豆漿 + 蜂蜜(單價2.0) + 紅棗(單價3.0) --- 費用: 8.0
購買:黃豆豆漿 + 蜂蜜(單價2.0) + 紅棗(單價3.0) + 紅棗(單價3.0) --- 費用: 11.0
到這裡,在基礎豆漿種類上面,增加各種配料並且計算總價已經可以完成了
這就是裝飾者模式,下面我們來講講其理論
三 裝飾者模式理論
(一) 定義和理解
定義:裝飾模式就是在不改變現有物件結構的情況下,動態地給該物件增加一些職責
就像上面豆漿加配料的例子中,裝飾模式就是為已經存在的內容,新增一些更加豐富的內容
還有例如當你的系統需要新的功能的時候,向舊程式碼中新增一些新的程式碼,這些新的程式碼就裝飾了原有類的核心職責或主要行為
這些新加入的內容,本質上自只是為了滿足一些在特定情況下才會需要的情況,例如並不是所有人都想要加蜂蜜等這些配料,對於這種需求,裝飾模式就是一種比較好的解決方案
從上面程式碼中也可以看出,當你需要在原有內容(豆漿)的基礎上新增裝飾內容(配料),只需要把每個要裝飾的內容放在一個單獨的類中,然後包裹要裝飾的物件,當需要執行新增配料這樣一種特殊行為的時候,就可以有選擇的進行裝飾,例如新增蜂蜜: drink = new Honey(drink);
就是用蜂蜜包裹了上面 new 出來的飲品 drink (這裡代表豆漿)
而且,當你想要新增新種類的豆漿的時候,例如紅豆豆漿,只要繼承豆漿這個父類,然後就可以享受到新增所有配料的權利
(二) 優缺點
優點:
-
有效的把類的核心職責和裝飾功能區分開了,而且可以去除相關類中重複的裝飾邏輯
-
裝飾是繼承的有力補充,比繼承靈活,動態擴充套件,即插即用
-
不同順序排列組合裝飾類可以實現不同效果
-
遵守開閉原則
缺點:
- 會增加許多子類,使得程式較複雜
(三) 結構
- 抽象構件(Component)角色:定義一個抽象介面(類)可以給這些物件動態的新增指責
- 具體構件(ConcreteComponent)角色:實現抽象構件,即一個具體的構件
- 抽象裝飾(Decorator)角色:繼承抽象構件,幷包含具體構件的例項,可以通過其子類擴充套件具體構件的功能,也就是上面配料那個抽象類
- 具體裝飾(ConcreteDecorator)角色:實現抽象裝飾的相關方法,也就是具體的配料,例如蜂蜜,紅棗