本文源碼見:https://github.com/get-set/get-designpatterns/tree/master/decorator
裝飾器模式(Decorator Pattern)以客戶端透明的方式擴展對象的功能。這種類型的設(shè)計模式屬于結(jié)構(gòu)型模式奥邮,它是作為現(xiàn)有的類的一個包裝植袍,是繼承關(guān)系的一個替代方案妓柜。
說到裝飾者模式性湿,估計大家都不陌生瓢谢,Java I/O的設(shè)計就是采用了裝飾者模式兼呵。想必初學Java I/O的時候大家都經(jīng)歷過一段“懵逼”期兔辅,各種InputStream
和OutputStream
層層嵌套,感覺就像洋蔥萍程,如果給裝飾者一個形象化的吉祥物幢妄,想必非洋蔥莫屬。
例子
這里裝飾者的例子就直接拿來主義了茫负,因為《圖解設(shè)計模式》這本書中的例子比較形象蕉鸳。
這個例子的功能是給文字增加裝飾邊框。通過不同的裝飾類實現(xiàn)不同的邊框效果。
比如潮尝,我有個字符串“Hello, world!”榕吼,我想在兩側(cè)加上邊框,那么就顯示為
|Hello, world!|
如果我想在上下左右均加上邊框勉失,那么就顯示為
+-------------+
|Hello, world!|
+-------------+
并且可以一層層嵌套:
+---------------+
|+-------------+|
||Hello, world!||
|+-------------+|
+---------------+
怎么樣羹蚣,是不是很像洋蔥啊乱凿?
總體來說顽素,還是字符顯示的問題,通過一個抽象類Display
來定義徒蟆。
Display.java
public abstract class Display {
public abstract int getColumn();
public abstract int getRows();
public abstract String getRowText(int row);
public final void show() {
for (int i = 0; i < getRows(); i++) {
System.out.println(getRowText(i));
}
}
}
其中show()
方法將所有行的內(nèi)容顯示出來胁出。那么對于沒有任何邊框的文字顯示來說:
StringDisplay.java
public class StringDisplay extends Display {
private String string;
public StringDisplay(String string) {
this.string = string;
}
public int getColumn() {
return string.getBytes().length;
}
public int getRows() {
return 1;
}
public String getRowText(int row) {
if (row == 0) {
return string;
} else {
return null;
}
}
}
因為只有一行,所以getRows()
返回1段审。我們再來看一下邊框裝飾后的文本:
BoardStringDisplay.java
public abstract class BoardStringDisplay extends Display {
protected Display display;
protected BoardStringDisplay(Display display) {
this.display = display;
}
}
SideBoardStringDisplay.java
public class SideBoardStringDisplay extends BoardStringDisplay {
protected SideBoardStringDisplay(Display display) {
super(display);
}
public int getColumns() {
return 1 + display.getColumns() + 1; // 文字兩側(cè)各增加一個字符
}
public int getRows() {
return display.getRows(); // 行數(shù)不變
}
public String getRowText(int row) {
return "|" + display.getRowText(row) + "|";
}
}
FullBoardStringDisplay.java
public class FullBoardStringDisplay extends BoardStringDisplay {
protected FullBoardStringDisplay(Display display) {
super(display);
}
public int getColumns() {
return 1 + display.getColumns() + 1;
}
public int getRows() {
return 1 + display.getRows() + 1;
}
public String getRowText(int row) {
if (row == 0 || row == display.getRows() + 1) {
return "+" + makeLine('-', display.getColumns()) + "+";
} else {
return "|" + display.getRowText(row - 1) + "|";
}
}
private String makeLine(char ch, int count) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < count; i++) {
buf.append(ch);
}
return buf.toString();
}
}
試一下洋蔥效果:
Client.java
public class Client {
public static void main(String[] args) {
Display d1 = new StringDisplay("Hello, world!");
Display d2 = new SideBoardStringDisplay(d1);
Display d3 = new FullBoardStringDisplay(
new SideBoardStringDisplay(
new FullBoardStringDisplay(d2)));
System.out.println("顯示字符串>>>>>>");
d1.show();
System.out.println("\n增加兩側(cè)邊框>>>>>>");
d2.show();
System.out.println("\n再增加全邊框全蝶、兩側(cè)邊框、全邊框>>>>>>");
d3.show();
}
}
輸入如下:
總結(jié)
這個例子的代碼量比以前的多一些寺枉,但是思路并不復雜抑淫。
這里,StringDisplay
是被裝飾者姥闪,SideBoardStringDisplay
和FullBoardStringDisplay
是裝飾器始苇,同時也能夠被裝飾,因為說到底甘畅,它們都是繼承自Display
埂蕊,所以可以層層嵌套,不斷增強疏唾。
我們再回頭看裝飾器模式的特點:
- 接口透明蓄氧,在不改變被裝飾者的前提下增加功能。無論是裝飾器還是被裝飾者槐脏,都有共同的抽象喉童,也許是繼承同一個抽象類,也許是實現(xiàn)同一個接口顿天;如此一來堂氯,裝飾前后的對象都是對外提供同樣的服務。就像生日蛋糕牌废,無論是裝飾了草莓咽白、還是慕斯、還是巧克力鸟缕,都還是蛋糕晶框,不會變成一塊披薩排抬。
- 使用了委托裝飾器將被裝飾的對象作為成員,使得類之間稱為弱關(guān)聯(lián)關(guān)系授段,這一點和橋接模式的出發(fā)點是一致的蹲蒲。草莓點綴在生日蛋糕上是草莓生日蛋糕,點綴在披薩上是水果披薩侵贵,客人不能搶了主人風頭届搁。這里和代理模式有些類似,主要區(qū)別在于應用場景和目的窍育,裝飾器模式應當為所裝飾的對象提供增強功能卡睦,而代理模式對被代理的對象施加控制,并不提供對對象本身的增強漱抓。
各種裝飾器并非一定要有一個抽象(本例中的BoardStringDisplay
)么翰,直接裝飾StringDisplay
也是OK的,這個并不是裝飾器模式的特點辽旋,只是具體使用時看是否有進一步抽象的需要。