設計模式之裝飾模式
設計模式之裝飾模式
裝飾模式非常強調實現技巧,我們一般用它應對類體系快速膨脹的情況。
在專案中,是什麼原因導致型別體系會快速膨脹呢?在多數情況下是因為我們經常要為型別增加新的職責(功能),尤其在軟體開發和維護階段,這方面需求更為普遍。
物件導向中每一個介面代表我們看待物件的一個特定方面。在Java編碼實現過程中由於受到單繼承的約束,我們通常也會將期望擴充套件的功能定義為新的介面,進而隨著介面不斷增加,實現這些介面的子類也在快速膨脹,如新增3個介面的實現,就需要8個型別(包括MobileImpl),4個介面則是16個型別,這種幾何基數的增長我們承受不了。為了避免出現這種情況,之前我們會考慮採用組合介面的方式解決,但客戶程式又需要從不同角度看待組合後的型別,也就是可以根據里氏替換原則用某個介面呼叫這個子類。所以面臨的問題是,既要has a、又要is a,裝飾模式解決的就是這類問題。
經典回顧
裝飾模式的意圖非常明確:動態為物件增加新的職責。
這裡有兩個關鍵詞:動態和增加,也就是說,這些新增的功能不是直接從父類繼承或是硬編碼寫進去的,而是在執行過程中透過某種方式動態組裝上去的。例如:我們在錄入文字的時候,最初只需要一個Notepad功能的軟體,然後增加了很多需求:
字型可以加粗。
文字可以顯示為不同顏色。
字號可以調整。
字間距可以調整。
……
不僅如此,到底如何使用這些新加功能,需要在客戶使用過程中進行選擇,也就是說,新的職責(功能或成員)是需要動態增加的。為了解決這類問題,裝飾模式給出的解決方案如圖12-1所示。
根據Notepad的示例要求,設計如圖12-2所示的靜態結構。
區別於經典裝飾模式設計,在圖12-2中我們將Decorator定義為具體類,而不是抽象類,主要是為了簡化示例結構。
首先,我們需要的是顯示文字,這時可以指定一個名為Text的介面,它有名為Content的讀/寫屬性。
然後,我們把所有需要用來裝飾的型別抽象出一個名為Decorator的介面,它繼承自這個Text,因此其實體類必須實現Content屬性方法。
接著,我們把沒有任何新增功能的Text做出一個“毛坯”的實體型別,命名為TextImpl。
最後,我們把操作字型bold()、setColor()的方法填充到每個具體的裝飾型別中。
這樣,概念上當TextImpl需要某種新增功能時,直接為其套上某個具體裝飾型別就可以了。這就好像給TextImpl類穿上一層又一層的“馬甲”。該模式這麼做主要是為了適用於哪些情景呢?
在不影響其他物件的情況下,以動態、透明的方式給單個物件新增職責。
畢竟客戶程式依賴的僅僅是Component介面,至於這個介面被做過什麼裝飾,只有實施裝飾的物件才知道,而客戶程式只是依據Component的介面方法呼叫它,這樣,裝飾類在給Component穿上新“馬甲”的同時也會隨著客戶程式的呼叫一併執行了。
遮蔽某些職責,也就是在套用某個裝飾型別時,並不增加新的特徵,只把既有方法遮蔽。
也就是說,裝飾類不僅能否充當“馬甲”,也能起到“口罩”的作用,讓Component現有的某些方法“閉嘴”。儘管我們使用裝飾模式一般是為了增加功能(做“加法”),但並不排斥它也具有方法遮蔽的作用(做“減法”),只不過平時用的比較少而已。
避免因不斷調整職責導致型別快速膨脹的情況。
下面看一段示例程式碼:
Java 抽象部分
public interface Text {
String getContent();
void setContent(String content);
}
/** implements Text 說明Decorator is a Text*/
public class Decorator implements Text{
/** 構造方式注入has a的text介面 */
private Text text;
public Decorator(Text text){
this.text = text;
}
@Override
public String getContent(){
return text.getContent();
}
@Override
public void setContent(String content){
text.setContent(content);
}
}
Java 具體裝飾型別
/**
* 具體裝飾類,屬於“馬甲”
*/
class BoldDecorator extends Decorator{
public BoldDecorator(Text text){
super(text);
}
public String bold(String data){
return "" + data + "";
}
@Override
public String getContent() {
return bold(super.getContent());
}
@Override
public void setContent(String content) {
super.setContent(content);
}
}
/**
* 具體裝飾類
* 屬於“馬甲”
*/
class ColorDecorator extends Decorator{
public ColorDecorator(Text text){
super(text);
}
public String setColor(String data){
return "
}
@Override
public String getContent() {
return setColor(super.getContent());
}
@Override
public void setContent(String content) {
super.setContent(content);
}
}
/**
* 具體裝飾類
* 屬於“口罩”
*/
class BlockAllDecorator extends Decorator{
public BlockAllDecorator (Text text){
super(text);
}
@Override
public String getContent() {
return null;
}
@Override
public void setContent(String content) {
super.setContent(content);
}
}
Unit Test
Text text;
@Before
public void setUp(){
text = new TextImpl();
}
/** 驗證套用單個裝飾物件的效果 */
@Test
public void testSingleDecorator(){
text = new Decorator(text);
//下面開始套用裝飾模式,開始給text穿“馬甲”
text = new BoldDecorator(text);
text.setContent("H");
assertEquals("H", text.getContent());
}
/** 驗證套用多個裝飾物件的效果 */
@Test
public void testMultipleDecorators(){
text = new Decorator(text);
//下面開始套用裝飾模式,開始給text穿“馬甲”
text = new BoldDecorator(text);
text = new ColorDecorator(text);
text = new BoldDecorator(text);
text.setContent("H");
assertEquals("
}
/** 驗證裝飾型別的撤銷(/“口罩”)效果*/
@Test
public void testBlockEffectDecorator(){
text = new Decorator(text);
//下面開始套用裝飾模式,開始給text穿“馬甲”
text = new BoldDecorator(text);
text = new ColorDecorator(text);
text.setContent("H");
assertEquals("
//下面開始套用裝飾模式,開始給text戴“口罩”
text = new BlockAllDecorator(text);
assertNull(text.getContent());
}
從上面的示例中不難看出,裝飾模式實現上特別有技巧,也很“八股”,它的宣告要實現Component定義的方法,但同時也會保留一個對Component的引用,Component介面方法的實現其實是透過自己儲存的Component成員完成的,而裝飾類只是在這個基礎上增加一些額外的處理。而且,使用裝飾模式不僅僅是為了“增加”新的功能,有時候我們也用它“撤銷”某些功能。專案中我們有3個要點必須把握:
Component不要直接或間接地使用Decorator,因為它不應該知道Decorator的存在,裝飾模式的要義在於透過外部has a + is a的方式對目標型別進行擴充套件,對於待裝飾物件本身不應有太多要求。
Decorator也僅僅認識Component。抽象依賴於抽象、知識最少。
某個ConcreteDecorator最好也不知道ComponentImpl的存在,因為ConcreteDecorator只能服務於這個ComponentImpl(及其子類)。
此外,使用裝飾模式解決一些難題的同時,我們也要看到這個模式的缺點:
開發階段需要編寫很多ConcreteDecorator型別。
執行時動態組裝帶來的結果就是排查故障比較困難,從實際角度看,Component提交給客戶程式的是最外層ConcreteDecorator的型別,但它的執行過程是一系列ConcreteDecorator處理後的結果,追蹤和除錯相對困難。
在實際專案中,我們往往會將一些通用的功能做成裝飾型別單獨編譯,而且一般也鼓勵這麼做,因為可以減少重複開發,但這樣會人為增加排查和除錯的難度。好在反編譯Java byte code不是太難。
本文節選自《模式——工程化實現及擴充套件(設計模式Java 版)》一書。
本書詳細資訊:http://space.itpub.net/?uid-13164110-action-viewspace-itemid-720925
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/13164110/viewspace-720926/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 【設計模式之裝飾模式】設計模式
- 設計模式系列之「裝飾模式」設計模式
- 設計模式之-裝飾器模式設計模式
- 設計模式之【裝飾器模式】設計模式
- 設計模式之裝飾者模式設計模式
- 設計模式之裝飾者模式(二)設計模式
- Go 設計模式之裝飾器模式Go設計模式
- 設計模式之裝飾者模式(一)設計模式
- PHP設計模式之裝飾者模式PHP設計模式
- Java設計模式之裝飾者模式Java設計模式
- java設計模式之裝飾器模式Java設計模式
- 我學設計模式 之裝飾模式設計模式
- 設計模式——裝飾模式設計模式
- 設計模式-裝飾模式設計模式
- 設計模式-裝飾設計模式設計模式
- 設計模式之裝飾器模式(decorator pattern)設計模式
- 【趣味設計模式系列】之【裝飾器模式】設計模式
- Golang 常見設計模式之裝飾模式Golang設計模式
- PHP設計模式之裝飾器模式(Decorator)PHP設計模式
- android常用設計模式之裝飾模式Android設計模式
- JAVA設計模式之 裝飾模式【Decorator Pattern】Java設計模式
- Java設計模式之裝飾模式趣談Java設計模式
- Java學設計模式之裝飾器模式Java設計模式
- 設計模式----裝飾器模式設計模式
- 設計模式-裝飾者模式設計模式
- 設計模式——裝飾者模式設計模式
- 設計模式-裝飾器模式設計模式
- [設計模式] 裝飾器模式設計模式
- 設計模式(八):裝飾模式設計模式
- [設計模式]裝飾者模式設計模式
- 8.java設計模式之裝飾者模式Java設計模式
- Java設計模式之裝飾者模式(Decorator pattern)Java設計模式
- 設計模式(十一)----結構型模式之裝飾者模式設計模式
- 裝飾設計模式設計模式
- 小白設計模式:裝飾者模式設計模式
- 設計模式--裝飾模式(Decorator Pattern)設計模式
- 設計模式(八)裝飾器模式設計模式
- 設計模式-裝飾模式(Decorator Pattern)設計模式