Java —— Decorator 裝飾器模式
結構型模式
裝飾邊框與被裝飾物的一致性
簡介
首先看一個比喻:
假設有一塊蛋糕,塗上奶油,其它什麼有不加,就是奶油蛋糕。如果加上草莓,就是草莓蛋糕。如果再
加上一塊黑色巧克力,上面用白色巧克力寫上名,然後插上蠟燭,就變成一塊生日蛋糕。
無論是蛋糕、奶油蛋糕、草莓蛋糕還是生日蛋糕,它們的核心都是蛋糕。
允許一個現有對象,添加新的功能,同時,不改變其結構。
這種模式創建了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。
用處
動態地給一個對象添加一些額外的指責。就增加功能來說,裝飾器模式較生成子類更爲靈活。
簡單例子
示例中的程序功能是給文字添加裝飾邊框。這裏所謂的裝飾邊框是指用“-” 、“+” 、“|” 等字符組成的邊框:
+-------------+
| Hello,world.|
+-------------+
結構
說明 | |
---|---|
Display | 用於顯示字符串的抽象類 |
StringDisplay | 用於顯示單行字符串的類 |
Border | 用於顯示裝飾邊框的抽象類 |
SideBorder | 用於顯示左右邊框的類 |
FullBorder | 用於顯示上下左右邊框的類 |
Main | 測試類 |
代碼
- Display
顯示多行字符串的抽象類
public abstract class Display {
/**
* 獲取橫向字符數
* @return
*/
public abstract int getColumns();
/**
* 獲取縱向行數
* @return
*/
public abstract int getRows();
/**
* 獲取row行的字符串
* @param row
* @return
*/
public abstract String getRowText(int row);
/**
* 顯示全部
*/
public final void show() {
for(int i=0; i<getRows(); i++) {
System.out.println(getRowText(i));
}
}
}
- StringDisplay
顯示單行字符串的類,肩負着Display類中聲明的抽象方法
public class StringDisplay extends Display {
/**
* 要顯示的字符串
*/
private String string;
/**
* 通過參數傳入顯示的字符串
* @return
*/
public StringDisplay(String string) {
this.string = string;
}
/**
* 字節數
* @return
*/
@Override
public int getColumns() {
return string.getBytes().length;
}
/**
* 行數是1
* @return
*/
@Override
public int getRows() {
return 1;
}
/**
* 僅當row爲0時返回值
* @param row
* @return
*/
@Override
public String getRowText(int row) {
if(row == 0) {
return string;
} else {
return null;
}
}
}
- Border
裝飾邊框的抽象類【通過繼承,裝飾邊框與被裝飾物具有了相同的方法】
public abstract class Border extends Display {
/**
* 表示被裝飾物
*/
protected Display display;
/**
* 在生成實例時通過參數指定被裝飾物
* @param display
*/
protected Border(Display display) {
this.display = display;
}
}
- SideBorder
具體的裝飾邊框 "|"
public class SideBorder extends Border {
/**
* 表示裝飾邊框的字符
*/
private char borderChar;
/**
* 通過構造函數指定Display和裝飾邊框字符
* @param display
* @param ch
*/
public SideBorder(Display display, char ch) {
super(display);
this.borderChar = ch;
}
/**
* 字符數爲字符串字符數加上兩側邊框字符數
* @return
*/
@Override
public int getColumns() {
return 1 + display.getColumns() + 1;
}
/**
* 行數即被裝飾物的行數
* @return
*/
@Override
public int getRows() {
return display.getRows();
}
/**
* 指定的那一行的字符串爲被裝飾物的字符串加上兩側的邊框的字符
* @param row
* @return
*/
@Override
public String getRowText(int row) {
return borderChar + display.getRowText(row) + borderChar;
}
}
- FullBorder
在字符串上下左右都加上裝飾邊框
public class FullBorder extends Border {
public FullBorder(Display display) {
super(display);
}
/**
* 字符數爲被裝飾物的字符數加上兩側邊框字符數
* @return
*/
@Override
public int getColumns() {
return 1 + display.getColumns() + 1;
}
/**
* 行數爲被裝飾物的行數加上上下邊框的行數
* @return
*/
@Override
public int getRows() {
return 1 + display.getRows() + 1;
}
/**
* 指定的那一行的字符串
* @param row
* @return
*/
@Override
public String getRowText(int row) {
if(row == 0) {
// 下邊框
return "+" + makeLine('-', display.getColumns()) + "+";
} else
if(row == display.getRows() + 1) {
// 上邊框
return "+" + makeLine('-', display.getColumns()) + "+";
} else {
// 其它邊框
return "|" + display.getRowText(row - 1) + "|";
}
}
/**
* 生成一個重複count次字符ch的字符串
* @param ch
* @param count
* @return
*/
private String makeLine(char ch, int count) {
StringBuffer buf = new StringBuffer();
for(int i=0; i<count; i++) {
buf.append(ch);
}
return buf.toString();
}
}
- Main
public class Main {
public static void main(String[] args) {
// 顯示單行字符串的類(相當於蛋糕的核心蛋糕[蛋糕胚子])
Display b1 = new StringDisplay("Hello, world.");
// 具體的裝飾邊框 "|"
Display b2 = new SideBorder(b1, '#');
// 在字符串上下左右都加上裝飾邊框
Display b3 = new FullBorder(b2);
b1.show();
b2.show();
b3.show();
Display b4 = new SideBorder(
new FullBorder(
new FullBorder(
new SideBorder(
new FullBorder(
new StringDisplay("你好, 世界。")
),
'*'
)
)
),
'/'
);
b4.show();
}
}
- 運行結果
Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
/+-----------------+/
/|+---------------+|/
/||*+-----------+*||/
/||*|你好, 世界。|*||/
/||*+-----------+*||/
/|+---------------+|/
/+-----------------+/
- Main時序圖
涉及角色
- Component
增加功能時核心角色。
以上述比喻來說,裝飾前的蛋糕就是Component角色。Component角色只是定義蛋糕的接口(API)。
示例中,由Display代表此角色。 - ConcreteComponent
實現Component角色定義的具體蛋糕。
示例中,StringDisplay代表此角色。 - Decorator(裝飾物)
具有與Component角色相同的接口(API)。在它內部保存了被裝飾對象 —— Component角色。Decorator知道自己要裝飾的對象。
示例中,Border代表此角色。 - ConcreteDecorator(具體的裝飾物)
具體的Decorator角色。
示例中,SideBorder和FullBorder代表此角色。
相關的設計模式
- Adapter 適配器模式
Decorator裝飾器模式可以在不改變被裝飾物的接口(API)的前提下,爲被裝飾物添加邊框(透明性)。
Adapter適配器模式用於適配兩個不同的接口(API)。 - Strategy 策略模式
Decorator裝飾器模式可以像改變被裝飾物的邊框或是爲被裝飾物添加多重邊框那樣,來增加類的功能。
Strategy 策略模式通過整體地替換算法來改變類的功能。
應用實例
- 孫悟空有 72 變,當他變成"廟宇"後,他的根本還是一隻猴子,但是他又有了廟宇的功能。
- 不論一幅畫有沒有畫框都可以掛在牆上,但是通常都是有畫框的,並且實際上是畫框被掛在牆上。在掛在牆上之前,畫可以被蒙上玻璃,裝到框子裏;這時畫、玻璃和畫框形成了一個物體。
優點
裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴展一個實現類的功能。
缺點
多層裝飾比較複雜。
使用場景
- 擴展一個類的功能。
- 動態增加功能,動態撤銷。
注意事項
可代替繼承。