Beverage是店裡所有飲料的抽象類,下面是飲料的不同口味。
在日常生活中,你在購買時,可能還會加一些小料(湊單滿減),例如燕奶(Steamed Milk)、豆漿(Soy)、摩卡(Mocha)等,在付款時,電腦的訂單系統會根據你點的飲料和加的小料計算出總的價錢。
我們現在就是要設計一個能自動計算價格的訂單系統。
最簡單的方法,就是所有的飲料包括小料都寫一個實現類,但是這樣在後期就是一個維護噩夢。不說可能有幾百種飲料幾百種實現方式,如果後期稍微改動其中一款小料的價格,那麼你就需要到一個一個的實現類裡面去進行修改,嚴重違反了軟體的設計原則。
tips:
程式碼應該如同晚霞中的蓮花一樣地關閉(免於改變),如同晨曦中的蓮花一樣地開放(能夠擴充套件)。
設計原則:
類應該對擴充套件開放,對修改關閉。
這樣的設計具有彈性可以應對改變,可以接受新的功能來應對改變的需求。
注意:在選擇需要被擴充套件的程式碼部分時要小心。每個地方都採用開放-關閉原則,是一種浪費,也沒有必要,還會導致程式碼變得複雜且難以理解,要找到平衡點。
在上面星巴茲咖啡的設計中,實現類數量爆炸、設計死板、以及基礎類別加入新功能不適用於所有的子類。
用裝飾者模式進行設計
拿一個深色烘焙咖啡(DarkRoast
)物件
以摩卡(Mocha)物件裝飾它
以奶泡(Whip)物件裝飾它
呼叫cost()方法,並委託(delegate)將調料的價錢加上去
簡單來講就是將物件一層一層包起來,在呼叫的時候,先一層一層進去,之後一層一層計算結果出來。
說明:動態地將責任附加到物件上。若要擴充套件功能,裝飾者提供了比繼承更有彈性的替代方案。
使用裝飾者模式設計星巴茲類圖
注意:這裡使用繼承是達到「型別匹配」的目的(!!!),而不是利用繼承來獲得行為。
如果有一張單子點的是:「雙倍摩卡豆漿奶泡拿鐵咖啡」,進行設計實現。
流程
核心程式碼實現
總抽象類,裝飾類與被裝飾類都實現此類,達到型別匹配
/**
* @Description 抽象類飲料
* @Author lh
* @Date 2022/12/6 19:31
*/
public abstract class Beverage {
public String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
被裝飾類,不同口味飲料
/**
* @Description 義大利濃縮咖啡
* @Author lh
* @Date 2022/12/6 19:37
*/
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}
/**
* @Description 家庭混合咖啡
* @Author lh
* @Date 2022/12/6 19:39
*/
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "House Blend Coffee";
}
裝飾抽象類
/**
* @Description 裝飾類調料抽象類
* @Author lh
* @Date 2022/12/6 19:35
*/
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
裝飾類實現
/**
* @Description 調料摩卡
* @Author lh
* @Date 2022/12/6 19:41
*/
public class Mocha extends CondimentDecorator{
private final Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
/**
* @Description 調料奶泡
* @Author lh
* @Date 2022/12/6 19:51
*/
public class Whip extends CondimentDecorator{
private final Beverage beverage;
public Whip(Beverage beverage) {
this.beverage = beverage;
}
實現 雙倍摩卡豆漿奶泡拿鐵咖啡
/**
* @Description 星巴茲計算
* @Author lh
* @Date 2022/12/6 20:10
*/
public class StarbuzzCoffee {
public static void main(String[] args) {
Beverage beverage = new HouseBlend();
System.out.println(beverage.getDescription() + ":" + beverage.cost() + "元");
Beverage beverage1 = new HouseBlend();
beverage1 = new Mocha(beverage1);
beverage1 = new Mocha(beverage1);
beverage1 = new Whip(beverage1);
System.out.println(beverage1.getDescription() + ":" + beverage1.cost() + "元");
}
}
下面是一個典型的物件集合,用裝飾者來將功能結合起來,以讀取檔案資料。
和星巴茲的設計相比,java.io
其實並沒有多大的差距。
核心程式碼範例
/**
* @Description 獲取文字行數
* @Author lh
* @Date 2022/12/7 19:34
*/
public class LowerNumberInputStream extends FilterInputStream {
public LowerNumberInputStream(InputStream in) {
super(in);
}
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toLowerCase(c));
}
public int read(byte[] b, int offset, int len) throws IOException {
int result = super.read(b, offset, len);
for (int i = offset; i < offset + result; i++) {
b[i] = (byte) Character.toLowerCase(b[i]);
}
return result;
}
}
/**
* @Description IO測試
* @Author lh
* @Date 2022/12/7 19:40
*/
public class InputTest {
public static void main(String[] args) throws FileNotFoundException {
int c;
try {
InputStream in = new LowerNumberInputStream(new BufferedInputStream(new FileInputStream("text.txt")));
while ((c = in.read()) >= 0) {
System.out.println(c);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
裝飾者和被裝飾者物件有相同的超型別(型別匹配)。
你可以用一個或多個裝飾者包裝一個物件。
既然裝飾者和被裝飾者有相同的超型別,所以在任何需要原始物件(被包裝的)場合,可以用裝飾過的物件代替它。
裝飾者可以在所委託被裝飾者的行為之前與之後,加上自己的行為,以達到特定的目的。
物件可以在任何時候被裝飾,所以可以在執行時動態地、不限量得使用你喜歡的裝飾者來裝飾物件。
程式碼地址