本文原創地址:jsbintask的博客(食用效果最佳),轉載請註明出處!
同系列文章:
從未這麼明白的設計模式(二):觀察者模式
從未這麼明白的設計模式(一):單例模式
前言
裝飾器模式是爲了運行時動態的擴展一個類的功能。它謹遵開閉原則
,它實現的關鍵在於繼承和組合的結合使用
,解耦對象之間的關係。
各種設計模式學習地址:
https://github.com/jsbintask22/design-pattern-learning
栗子
首先我們列舉一個案例,並且按照面向對象的思想來對應實體之間的關係。
有一個咖啡店,銷售各種各樣的咖啡,拿鐵,卡布奇洛,藍山咖啡等,在沖泡前,會詢問顧客是否要加糖,加奶,加薄荷等。這樣不同的咖啡配上不同的調料就會賣出不同的價格。
V1
針對上面的栗子,我們很容易就抽象出對應的實現,如上圖。接着,我們就要編寫對應的類來實現對應的功能。在這個例子中,主題當然就是咖啡
,並且它有一個屬性是名字
,一個行爲 價格
,出於“面向對象”的思想,我們自然會設計出抽象類Coffee
:
public abstract class Coffee {
/**
* 獲取咖啡得名字
*/
public abstract String getName();
/**
* 獲取咖啡的價格
*/
public abstract double getPrice();
}
接着,按照繼承的思想,我們要開始設計出具體的實現類,因爲拿鐵,卡布奇洛,藍山搭配上不同的調料(上面三種)會有不同的價格,名字,所以我們至少得設計出 3 X 3 = 9 個類來分別對應它們的名字和價格:
嗯!我想不用說這樣設計得缺陷也很明顯了! 由於不同的咖啡和不同的調料得各種任意組合,使得出現了
類爆炸
的現象。既然有這麼明顯的缺陷,那我們當然得改! 我們可以考慮把各種調料當作屬性加入到Coffee這個抽象類中,接着在實現類中計算價格和名字時,分別判斷是否加入了各種調料包,得到不同的名字和價格!
按照上面的思想,我們的Coffee類現在變成了這樣:
public abstract class Coffee {
// 是否加了牛奶
protected boolean addedMilk;
// 是否加了糖
protected boolean addedSugar;
// 是否加了薄荷
protected boolean addedMint;
/**
* 獲取咖啡得名字
*/
public abstract String getName();
/**
* 獲取咖啡的價格
*/
public abstract double getPrice();
}
接着,我們實現一種咖啡,藍山咖啡:
public class BuleCoffee extends Coffee {
@Override
public String getName() {
StringBuilder name = new StringBuilder();
name.append("藍山");
if (addedMilk) {
name.append("牛奶");
}
if (addedMilk) {
name.append("薄荷");
}
if (addedSugar) {
name.append("加糖");
}
return name.toString();
}
@Override
public double getPrice() {
double price = 10;
if (addedMilk) {
price += 1.1;
}
if (addedMilk) {
price += 3.2;
}
if (addedSugar) {
price += 2.7;
}
return price;
}
}
嗯!現在似乎比上面愉快多了。其實不然!我們仔細分析這種設計,會發現它似乎不太符合”封裝的思想“,比如說針對拿鐵,對於加薄荷而言,對他總是多餘的! 而對於藍山而言,牛奶又顯得很多餘! 所以這種設計也並不合理。 另外,我們假設coffee,拿鐵等實體類來自第三方類庫,我們並不能改動這些類的實現, 又要怎麼得到名字和價格呢?
這個時候,我們就得使用裝飾器
模式來動態的擴展類行爲! 所以我們設計出V3版本。
V3
開閉原則
首先,我們需要了解一個面向對象的一個基本設計原則:開閉原則
,它指的是類應該對修改關閉,對擴展開放
。
怎麼理解呢? 就比如我們上方說的:假如cofee和它的一衆實現拿鐵,卡布奇洛,藍山來自第三方類庫,並且這個類庫已經很”適合“,”實用“了。 而我們爲了得到加入不同調料的咖啡的名字和價格,我們就得修改這些實現,而這樣的修改,總是免不了穩定性
的改變。對原本的系統來說也是一種風險! 所以我們應該 對修改關閉,對擴展開放
;
繼承和組合
遵循開閉原則,那我們就得對外擴展,那怎麼對外擴展呢? 這也是裝飾器模式實現的關鍵,利用繼承和組合
的結合; 現在我們可以考慮設計出一個裝飾類,它也繼承自coffee,並且它內部有一個coffee的實例對象:
現在,我們多了一個
咖啡裝飾器
: CoffeeDecorator:
public abstract class CoffeeDecorator implements Coffee {
private Coffee delegate;
public CoffeeDecorator(Coffee coffee) {
this.delegate = coffee;
}
@Override
public String getName() {
return delegate.getName();
}
@Override
public double getPrice() {
return delegate.getPrice();
}
}
接着,我們將牛奶,薄荷作爲抽象出一個類,繼承自CoffeeDecorator,所以,現在類圖就成了這樣:
我們實現一個
MilkCoffeeDecorator
:
public class MilkCoffeeDecorator extends CoffeeDecorator {
public MilkCoffeeDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getName() {
return "牛奶, " + super.getName();
}
@Override
public double getPrice() {
return 1.1 + super.getPrice();
}
}
按同樣的方法可以實現出MintCoffeeDecorator
,SugarCoffeeDecorator
。接着我們寫一個測試類:
public class App {
public static void main(String[] args) {
// 得到一杯原始的藍山咖啡
Coffee blueCoffee = new BlueCoffee();
System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
// 加入牛奶
blueCoffee = new MilkCoffeeDecorator(blueCoffee);
System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
// 再加入薄荷
blueCoffee = new MintCoffeeDecorator(blueCoffee);
System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
// 再加入糖
blueCoffee = new SugarCoffeeDecorator(blueCoffee);
System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
}
}
從結果我們可以看出,隨着不斷加入各種調料,價格,名字都在改變! 這說明我們加入不同的調料,動態的改變了咖啡的名字和價格!
思考
從上面的最後的裝飾器模式的實現來看,我們可以得出以下結論:
- 通過裝飾器模式可以動態的將責任附加到原有的對象上,而不改變原有的code。
- 遵循
開閉原則
- 裝飾者和被裝飾者有相同的父類(如栗子中的Coffee)
- 可以用多個裝飾器裝飾同一個對象。(見運行類)
- 裝飾者可以在被裝飾者的行爲之前或之後動態的加上自己的行爲。(參考裝飾實現)
- 組合比繼承更加的靈活(上面的coffee代理)
擴展
到現在,我們已經實現了一個自己的裝飾器,我們來看看jdk中用到的裝飾器實現.
IO
我們可以查看FilterInputStream:
它的主要是實現者爲
BufferedInputStream
:所以我們經常可以使用BufferedInputStream裝飾一個InputStream,比如FileInputStream:
new BufferedInputStream(FileInputStream);
這就是裝飾器模式的典型應用。
tomcat
在tomcat的HttpServletRequest的內部實現代碼中,RequestFacde
繼承自HttpServlet,而它內部的實現也是通過代理Request
對象,而Request對象繼承自HttpServlet,Request內部代理了org.apache.coyote.Request
來實現的。
總結
裝飾器模式充分展示了組合的靈活。利用它來實現擴展。它同時也是開閉原則的體現。 如果相對某個類實現運行時功能動態的擴展。 這個時候你就可以考慮使用裝飾者模式!
關注我,這裏只有乾貨!