文章優先發表在個人博客:https://www.xdx97.com/article/706183532841533440
一、引入裝飾者模式
我們現在有一個賣咖啡的需求。我們有咖啡,我們也有配料(巧克力、牛奶…)。現在我們要設計一個程序計算用戶這杯咖啡的價格。(咖啡 + 配料)
1-1:思路一:排列組合
我們把每種組合都列舉出來。比如:咖啡+巧克力、咖啡+雙份巧克力、咖啡+牛奶 …
這樣的確很簡單的就達到了我們的需求但是有一個問題就是產生很多的類,當我們新增了一種配料的時候又會新增很多種組合,維護性差。
1-2:思路二:使用類組合
我們在每種咖啡裏面定義每個配料的屬性,比如我們有一個類:咖啡,在裏面把每種配料都作爲屬性存放進去
public class Coffee{
private String name;
private String price;
private int chocolates;
private int milk;
}
通過上面的例子我們可以看到每次計算價格的時候我們需要判斷對應的配料數是否大於0。
上面這種思路的確可以解決類很多的問題,但是我們新增一種配料的時候還是需要去修改之前的類,這不符合設計的OCP原則。
2、使用裝飾者模式
裝飾者模式: 動態的將新功能附加到對象上。在對象功能擴展方面,它比繼承更有彈性,裝飾者模式也體現了開閉原則(ocp)
當我們需要一個 咖啡 的時候:
- Coffee coffee = new Coffee();
當我們需要一個 咖啡 + 牛奶 的時候:
- Coffee coffee = new Coffee();
- coffee = new Milk(coffee );
當我們需要一個 咖啡 + 牛奶 + 巧克力 的時候:
- Coffee coffee = new Coffee();
- coffee = new Milk(coffee);
- coffee = new Chocolates(coffee);
可能這樣並不是那麼好理解,下面寫代碼。當你看完代碼再來看上面的圖和代碼就好理解了。
Drink
@Data
public abstract class Drink {
// 名稱
public String name;
// 價格
public float price;
// 返回費用
public abstract float cost();
// 訂單描述
public abstract String getDesc();
}
Coffee
public class Coffee extends Drink {
public Coffee(){
setName("咖啡");
setPrice(10f);
}
@Override
public float cost() {
return super.getPrice();
}
@Override
public String getDesc() {
return super.getName() + super.getPrice();
}
}
Decorator
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.getName() + super.getPrice() + drink.getDesc();
}
}
Milk
public class Milk extends Decorator {
public Milk(Drink drink) {
super(drink);
setName("牛奶");
setPrice(2f);
}
}
Chocolates
public class Chocolates extends Decorator {
public Chocolates(Drink drink) {
super(drink);
setName("巧克力");
setPrice(3f);
}
}
測試 (咖啡 + 牛奶 + 巧克力 + 牛奶)
public class Main {
public static void main(String[] args) {
Drink order = new Coffee();
order = new Milk(order);
order = new Chocolates(order);
order = new Milk(order);
System.out.println(order.cost());
System.out.println(order.getDesc());
// 打印結果
// 17.0
// 牛奶2.0巧克力3.0牛奶2.0咖啡10.0
}
}
分析
1、每個咖啡裏面都是單獨的,它的 cost() 就是返回當前自己的價格,它的 getDesc() 方法就是返回自己名字和單價。
2、每一個裝飾者,裏面都包含一個Drink,它的 cost() 返回自己的價格 + Drink的價格,它的 getDesc() 方法就是返回自己名字和單價 + Drink名字和單價。(這個Drink是父類,所以它可以代表咖啡、也可以代表配料)
按照上面的分析,我們不管要套多少層都沒得關係,反正是一個遞歸的方式輸出嘛。
拓展
思考一下:假如我們的這個咖啡下面還有子類,比如美式咖啡、拿鐵咖啡?那麼應該怎麼擴展呢?
我們只需要讓美式咖啡、拿鐵咖啡繼承咖啡就好了。然後 new 的時候我們不 new 咖啡,new 美式咖啡、拿鐵咖啡就好了。