定義
動(dòng)態(tài)地將責(zé)任附加到對象上。想要擴(kuò)展功能,裝飾者提供有別于繼承的另一種選擇。
例子
現(xiàn)在有一家咖啡店,需要設(shè)計(jì)一個(gè)咖啡的訂單系統(tǒng)。在最初咖啡種類較少的時(shí)候,設(shè)計(jì)了一個(gè)beverage的父類,所有的咖啡都是繼承自beverage的子類:

這個(gè)設(shè)計(jì)采用繼承的方式實(shí)現(xiàn)不同咖啡子類,現(xiàn)在需求有了變化,在購買咖啡的時(shí)候,可以在里面加入各種調(diào)料,比如牛奶,豆?jié){,摩卡等,這些調(diào)料也是有各自的價(jià)格。如何處理這個(gè)需求,并應(yīng)對可能的變化?
分析
我們可能有以下思路:
1.在現(xiàn)有設(shè)計(jì)下,增加新的子類。如豆?jié){濃縮咖啡,牛奶濃縮咖啡,豆?jié){深培咖啡等。
分析:這種方式本質(zhì)是窮舉法,只是理論可行而已。看起來是對咖啡 + 調(diào)料的排列組合,一一列舉出來作為一個(gè)子類。事實(shí)上,用戶完全可能需要不止一種的調(diào)料,保持這個(gè)設(shè)計(jì)將是災(zāi)難一樣。
2.上面思路不可行的原因在于,我們不知道可能會(huì)有多少組合以及按照什么樣的順序去組合。這里我們需要想明白一件事,即加了調(diào)料的飲料依然是飲料,繼續(xù)加調(diào)料還是飲料。這個(gè)事實(shí)給我們的啟發(fā)就是,我們可以設(shè)計(jì)出一個(gè)組合的對象,它組合了飲料對象和調(diào)料對象。使用起來大概是這樣的:
Beverage A = new Beverage(some beverage, milk);
A = new Beverage(A, soy);
A = new Beverage(A, suger);
...
3.上述組合對象的思想是沒問題的,只是實(shí)現(xiàn)起來有些問題。在上面的偽代碼可以看出, 設(shè)計(jì)的飲料對象需要知道所有的飲料類(咖啡,奶茶等)和所有的調(diào)料類(豆?jié){,牛奶,摩卡等)。這個(gè)問題可以用繼承或接口實(shí)現(xiàn),組合對象里包含兩個(gè)對象,分別繼承于(或?qū)崿F(xiàn)接口)飲料類和調(diào)料類
4.繼續(xù)優(yōu)化,從實(shí)際意義看,調(diào)料類是依賴于飲料的,即飲料是調(diào)料的必要不充分條件,所以沒必要設(shè)計(jì)一系列的調(diào)料對象,調(diào)料只是裝飾飲料用的。每一種調(diào)料是一種裝飾者,需要飲料來初始化,而這個(gè)裝飾器繼承自飲料類(或者實(shí)現(xiàn)飲料接口)。類圖如下:

裝飾者模式的關(guān)鍵在于,裝飾者和被裝飾者有著相同的超類型,這里用到繼承或者接口的手段目的都是為了做類型匹配,另外,裝飾者可以再所委托被裝飾者的行為之前或之后,加上自己的行為,以達(dá)到特定的目的,典型的例子就是java中的io類,裝飾者模式利用了組合對象來實(shí)現(xiàn)擴(kuò)展功能。
總結(jié)
- 繼承屬于擴(kuò)展形式之一,但不見得是達(dá)到彈性設(shè)計(jì)的最佳方式
- 在我們的設(shè)計(jì)中,應(yīng)該允許行為可以被擴(kuò)展,而無須修改現(xiàn)有的代碼
- 組合和委托可用于在運(yùn)行時(shí)動(dòng)態(tài)地加上新行為
- 除了繼承,裝飾者模式也可以讓我們擴(kuò)展行為
- 裝飾者模式意味著一群裝飾者類,這些類用來包裝具體組件
- 裝飾者類反映出被裝飾的組件類型(具有相同的類型)
- 可以使用無數(shù)個(gè)裝飾者包裝一個(gè)組件
- 裝飾者會(huì)導(dǎo)致設(shè)計(jì)中出現(xiàn)很多小對象,過度使用,會(huì)讓程序變得復(fù)雜