DevOps-Gitlab命令使用、實現程式碼上傳與下載示例
**裝飾者模式: **動態地將責任附加到物件上。若要擴充套件功能,裝飾者提供了比繼承更有彈性的替代方案。
原則: 對擴充套件開放,對修改關閉。
問題引入:
購買咖啡時,也可以要求在其中加入各種調料,例如:蒸奶(Steamed Milk)、豆漿(Soy)、摩卡(Mocha,也就是巧克力風味)或覆蓋奶泡。星巴茲會根據所加入的調料收取不同的費用。所以訂單系統必須考慮到這些調料部分。
帶來的問題: 類數量爆炸、設計死板,以及基類加入的新功能並不適用於所有的子類。
解決方法:
以飲料為主體,然後在執行時以調料來“裝飾”(decorate)飲料。
比方說,如果顧客想要摩卡和奶泡深焙咖啡,那麼,要做的是:
1 拿一個深焙咖啡(DarkRoast)物件
2 以摩卡(Mocha)物件裝飾它
3 以奶泡(Whip)物件裝飾它
4 呼叫cost()方法,並依賴委託(delegate)將調料的價錢加上去。
目前已知歸納:
- 裝飾者和被裝飾物件有相同的超型別。
- 你可以用一個或多個裝飾者包裝一個物件。
- 既然裝飾者和被裝飾物件有相同的超型別,所以在任何需要原始物件(被包裝的)的場合,可以用裝飾過的物件代替它。
- 裝飾者可以在所委託被裝飾者的行為之前與/或之後,加上自己的行為,以達到特定的目的。
- 物件可以在任何時候被裝飾,所以可以在執行時動態地、不限量地用你喜歡的裝飾者來裝飾物件。
裝飾者模式 類圖
飲料類圖
大師: 我說蚱蜢呀!距離我們上次見面已經有些時日,你對於繼承的冥想,可有精進?
門 徒:是的,大師。儘管繼承威力強大,但是我體會到它並不總是能夠實現最有彈性和最好維護的設計。
大師:啊!是的,看來你已經有所長進。那麼,告訴我,我的門徒,不通過繼承又能如何達到複用呢?
門徒:大師,我已經瞭解到利用組合(composition)和委託(delegation)可以在執行時具有繼承行為的效果。
大師:好,好,繼續……
門徒:利用繼承設計子類的行為,是在編譯時靜態決定的,而且所有的子類都會繼承到相同的行為。然而,如果能夠利用組合的做法擴充套件物件的行為,就可以在執行時動態地進行擴充套件。
大師:很好,蚱蜢,你已經開始看到組合的威力了。
門徒:是的,我可以利用此技巧把多個新職責,甚至是設計超類時還沒有想到的職責加在物件上。而且,可以不用修改原來的程式碼。
大師:利用組合維護程式碼,你認為效果如何?
門徒:這正是我要說的。通過動態地組合物件,可以寫新的程式碼新增新功能,而無須修改現有程式碼。既然沒有改變現有程式碼,那麼引進bug或產生意外副作用的機會將大幅度減少。
大師:非常好。蚱蜢,今天的談話就到這裡。希望你能在這個主題上更深入……牢記,程式碼應該如同晚霞中的蓮花一樣地關閉(免於改變),如同晨曦中的蓮花一樣地開放(能夠擴充套件 )
Sue:這話怎麼說?
Mary:看看類圖。CondimentDecorator擴充套件自Beverage類,這用到了繼承,不是嗎?
Sue:的確是如此,但我認為,這麼做的重點在於,裝飾者和被裝飾者必須是一樣的型別,也就是有共同的超類,這是相當關鍵的地方。在這裡,我們利用繼承達到“型別匹配”,而不是利用繼承獲得“行為”。
Mary:我知道為何裝飾者需要和被裝飾者(亦即被包裝的元件)有相同的“介面”,因為裝飾者必須能取代被裝飾者。但是行為又是從哪裡來的?
Sue:當我們將裝飾者與元件組合時,就是在加入新的行為。所得到的新行為,並不是繼承自超類,而是由組合物件得來的。
Mary:好的。繼承Beverage抽象類,是為了有正確的型別,而不是繼承它的行為。行為來自裝飾者和基礎元件,或與其他裝飾者之間的組合關係。
Sue:正是如此。
Mary:哦!我明白了。而且因為使用物件組合,可以把所有飲料和調料更有彈性地加以混和與匹配,非常方便。
Sue:是的。如果依賴繼承,那麼類的行為只能在編譯時靜態決定。換句話說,行如果不是來自超類,就是子類覆蓋後的版本。反之,利用組合,可以把裝飾者混合著用……而且是在“執行時”。
Mary:而且,如我所理解的,我們可以在任何時候,實現新的裝飾者增加新的行為。如果依賴繼承,每當需要新行為時,還得修改現有的程式碼。
Sue:的確如此。
Mary:我還剩下一個問題,如果我們需要繼承的是component型別,為什麼不Beverage 類設計成一個介面,而是設計成一個抽象類呢?
Sue:關於這個嘛,還記得嗎?當初我們從星巴茲拿到這個程式時,Beverage“經”是一個抽象類了。通常裝飾者模式是採用抽象類,但是在Java中可以使用介面。儘管如此,通常我們都努力避免修改現有的程式碼,所以,如果抽象類運作得好好的,還是別去修改它。
程式碼實現
Beverage類
public abstract class Beverage {
String description = "Unknown Beverage";
static final int TALL = 0;
static final int GRANDE = 1;
static final int VENTI = 2;
int size = 1;
// 咖啡容量: 大、中、小
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getDescription() {
return description;
}
public abstract double cost();
}
Condiment(調料)抽象類
public abstract class CondimentDecorator extends Beverage{
@Override
public abstract String getDescription();
}
飲料 (濃縮咖啡Espresso )
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}
public double cost() {
return 1.99;
}
}
調料程式碼
// 摩卡是一個裝飾者,所以讓它擴充套件自CondimentDecorator。
// 讓Mocha(裝飾者)能夠引用一個Beverage(被裝飾者)
public class Mocha extends CondimentDecorator{
Beverage beverage; // 用一個例項變數記錄飲料,也就是被裝飾者。
// 讓被裝飾者(飲料)被記錄到例項變數中。
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public double cost() {
return .20 + beverage.cost();
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
}
// 調料根據咖啡容量收費 小中大杯的咖啡加上豆漿Soy,分別加收0.10、0.15、0.20美金
public class Soy extends CondimentDecorator{
Beverage beverage;
public Soy(Beverage beverage) {
this.beverage = beverage;
}
// 現在要把getSize()傳播到被包裝的飲料。因為所有的調料裝飾者都會用到這個方法,
// 所以也應該把它移到抽象類中。
@Override
public int getSize() {
return beverage.getSize();
}
@Override
public double cost() {
double cost = beverage.cost();
if (getSize() == Beverage.TALL) {
cost += .10;
} else if (getSize() == Beverage.GRANDE) {
cost += .15;
} else if (getSize() == Beverage.VENTI) {
cost += .20;
}
return cost;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Soy";
}
}
下訂單的測試程式碼
public class StarbuzzCoffee {
public static void main(String[] args) {
// 訂一杯Espresso,不需要調料,打印出它的描述與價錢
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription()
+ " $" + beverage.cost());
// 製造出一個DarkRoast物件。用Mocha、Mocha、Whip裝飾它
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription()
+ " $" + beverage2.cost());
Beverage beverage3 = new HouseBlend();
beverage3.setSize(Beverage.TALL); // 調整杯的大小
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println(beverage3.getDescription()
+ " $" + beverage3.cost());
}
}
真實世界的裝飾者:Java I/O
BufferedInputStream及LineNumberInputStream都擴充套件自FilterInputStream,而FilterInputStream是一個抽象的裝飾類。
編寫自己的Java I/0裝飾者
當讀取“I know the Decorator Pattern therefore I RULE!”,
裝飾者會將它轉成“i know the decorator pattern therefore i rule!”
實現方式 :擴充套件FilterInputStream類,並覆蓋read()方法
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in);
}
// 實現兩個read()方法, 一個針對位元組,一個針對位元組陣列
@Override
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toLowerCase((char) c));
}
@Override
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((char) b[i]);
}
return result;
}
}
// 測試類
public class InputTest {
public static void main(String[] args) throws IOException{
int c;
try {
InputStream in =
new LowerCaseInputStream(
new BufferedInputStream(
new FileInputStream("C:\\桌面\\test.txt")));
while ((c = in.read()) >= 0) {
System.out.print((char) c);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
要點
� 繼承屬於擴充套件形式之一,但不見得是達到彈性設計的最佳方式。
� 在我們的設計中,應該允許行為可以被擴充套件,而無須修改現有的程式碼。
� 組合和委託可用於在執行時動態地加上新的行為。
� 除了繼承,裝飾者模式也可以讓我們擴充套件行為。
� 裝飾者模式意味著一群裝飾者類 , 這 些 類 用 來 包 裝 具 體 元件。
� 裝飾者類反映出被裝飾的元件型別(事實上,他們具有相同的型別,都經過介面或繼承實現)。
� 裝飾者可以在被裝飾者的行為前面與/或後面加上自己的行為 , 甚 至 將 被 裝 飾 者 的 行 為整個取代掉,而達到特定的目的。
� 你可以用無數個裝飾者包裝一個元件。
� 裝飾者一般對元件的客戶是透明的,除非客戶程式依賴於元件的具體型別。
� 裝飾者會導致設計中出現許多小物件,如果過度使用,會讓程式變得很複雜。