1. 程式人生 > >設計模式(三):“花瓶+鮮花”中的裝飾者模式(Decorator Pattern)

設計模式(三):“花瓶+鮮花”中的裝飾者模式(Decorator Pattern)

在前兩篇部落格中詳細的介紹了"策略模式"和“觀察者模式”,今天我們就通過花瓶與鮮花的例子來類比一下“裝飾模式”(Decorator Pattern)。在“裝飾模式”中很好的提現了開放關閉原則,即類應該對擴充套件開放對修改關閉。裝飾者模式可以讓我們在不對原來程式碼的修改的情況下對類進行擴充套件。這也好比我們往花瓶裡插花,我們在插花的時候是不會對花瓶以及原來的話進行任何的修改,而只管將我們新的花新增進花瓶即可。這就是我們的裝飾者模式。當然本篇部落格中所採用的語言仍然是Swift語言。

裝飾者模式,用另一種表達方式就是“對原有的物體進行裝飾,給原有的物體新增上新的裝飾品”。舉個栗子,比如一個禮物,我們要對其進行包裝,禮物是被裝飾者(我們稱為元件---Component

),而包裝盒以及包裝盒上的花等等就是裝飾品(我們成為裝飾者---Decorator)。如果換成花瓶與鮮花的關係,花瓶就是Component,而鮮花就是Decorator。下方引用了裝飾者模式的定義:

裝飾者模式:動態地將責任附加到物件上。若要擴充套件功能,裝飾著提供了比繼承更有彈性的替代方案。

一、使用“類圖”分析鮮花+花瓶的裝飾關係

與之前部落格的風格類似,我們還是依託於例項來理解“裝飾者模式”,我們就依託於花瓶與鮮花的關係來理解一下裝飾者模式。在之前的部落格中我們提到過一條設計原則“封裝變化”,也就是說要將變化的東西進行封裝提取。在“裝飾者模式”中所使用的裝飾就是變化的部分,也就是Decorator是變化的部分

對應著我們的鮮花,因為往花瓶中插花的過程就是鮮花變化的過程,也就是為花瓶裝飾的過程。而花瓶就是元件了。

在“裝飾者模式”中需要注意的是,這裡所謂的裝飾者不單單就是我元件新增的新的裝飾品。一個裝飾者物件就是新增該裝飾後的元件,也就是說裝飾者=舊元件 + 新裝飾品,理解這一點是非常重要的。具體請看下方的元件與裝飾者之間的關係:

        

下方的類圖就是我們將要實現的“裝飾者模式”的例項,也就是鮮花和花瓶的關係。下方所有類的基類是VaseComponent(花瓶元件),在VaseComponent類中的description欄位是用來描述某某花瓶中裝有某某花的,display()方法用來列印description描述資訊的。上方的紅框就是所有的鮮花(裝飾者---Decorator),所有鮮花裝飾者的基類是FlowerDecorator

,當然FlowerDecorator也是繼承自VaseComponent(花瓶元件)的,因為裝飾者擁有被裝飾的物件同時又有新新增的裝飾物(見上圖)。在這些裝飾者類中包含一個欄位,該欄位就是VaseComponent的物件(花瓶元件的物件)。該物件可以指沒有任何裝飾的花瓶,也可以指已經添加了裝飾的花瓶。無論是裝飾者還是被裝飾者都有共同的基類,所以我們就可以利用多型來實現“裝飾者模式”。

 

在上面類圖的FlowerDecorator中的VaseComponent物件就對應著本部分第一張圖中裝飾者中包括的那個元件。該元件是最近一次裝飾過的元件,而裝飾者所負責的事情就是在該元件上新增上該裝飾者特有的裝飾品。換句話說,此時的裝飾者的物件就是最新的元件物件。也許經過這些原理的講解,你會有些迷惑,那麼不要著急,在具體程式碼實現時會帶你撥開雲霧見日出的。

二、“花瓶+鮮花”具體程式碼實現(Swift版)

當然,我們此處的程式碼實現與上面的“類圖”的設計是一致的。看完程式碼再結合著上面的“類圖”你會對裝飾者模式有更好的理解。下方我們會一步步的給出程式碼具體實現,當然下方的類名,成員變數以及成員方法的命名與上述類圖一直。

1.實現空花瓶的基類(VaseComponent

花瓶的基類VaseComponent就是我們被修飾者也就是我們所有花瓶(元件)的基類了。當然VaseComponent不僅僅是所有花瓶的基類,它還是所有裝飾者的基類,因為裝飾者物件 = 舊元件 + 新裝飾品 = 新元件。在該類中的description欄位中儲存的是花瓶的描述資訊,比如“瓷花瓶”,“玻璃花瓶”等資訊。getDescription()->String方法是用來獲取description儲存的描述資訊的。display()->Void方法就是對getDescription()方法獲取到的值進行列印, 具體實現如下所示。

    

2.建立我們的空花瓶

在第一步中我們建立了空花瓶的基類,緊接著我們要實現具體的花瓶。在下方程式碼中我們建立了兩個空花瓶,一個是Porcelain瓷花瓶,一個是Glass玻璃花瓶。並且在呼叫父類初始化器時為父類中的description欄位進行初始化。空花瓶比較簡單,程式碼也不多,空花瓶就是一個坯子,等著其他鮮花來做修飾,具體實現如下所示。

    

3. 鮮花基類(FlowerDecorator)的實現

FlowerDecorator也就是所有裝飾者的基類,這裡與其稱為鮮花基類,還不如成為所有“新花瓶”的基類。什麼是“新花瓶”呢?我們暫且成為添加了新的鮮花種類的花瓶為“新花瓶”。FlowerDecorator是所有鮮花裝飾者的基類,而FlowerDecorator繼承自VaseComponent類。在FlowerDecorator中添加了一個vase欄位,該欄位是VaseComponent型別,用於儲存“舊元件”,也就是上一次被修飾過的花瓶元件。這也是所有裝飾者都包含的欄位,“裝飾者”在初始化時會指定上次被修飾後元件(空花瓶或者其他修飾者的物件)。也就是說vase欄位中儲存的可以是一個空的花瓶物件,也可以是其他“裝飾者”類的物件。具體實現如下所示:

   

4.實現各個裝飾者(Decorator)

上方我們已經建立好了裝飾者的基類,在裝飾者基類中含有最新的元件(花瓶的狀態,還有多少種類的花)。而在“裝飾者”的類中我們要將具體花的品種,也就是我們變化的部分新增進具體的裝飾者的實現中。下方的第一個類是我們的玫瑰花Rose類,重寫了基類的getDescription()方法,在該方法中,為上一個裝飾者添加了新的裝飾品,也就是“玫瑰”。而在百合花Lily的類中我們為元件添加了“百合”裝飾品。具體實現方式如下所示:

   

三、“萬事俱備,只欠東風”--建立測試用例

經過上面的兩大步,我們的裝飾者模式的程式碼實現也就做完了。但是上面只是實現,沒有測試用例的驅動,上面的示例看上去不夠直觀。為了搞清楚其工作方式,我們的測試用例還是必不可少的。下方就是我們的測試用例的程式碼:

   

在上述程式碼中呢,我們首先建立了一個空的瓷花瓶的物件procelain,緊接著列印描述資訊(輸出“瓷花瓶”)。然後為該瓷花瓶的物件procelain新增上Rose和Lily裝飾。當然我們仍然使用procelain變數來接收新增Rose修飾後的物件(也就是Rose類的物件),此時Rose類的物件代表著“插有玫瑰花的瓷花瓶”。緊接著,我們在Rose物件的基礎上添加了Lily裝飾,新增Lily裝飾後,porcelain就是Lily類的物件,表示“插有玫瑰花,百合花的瓷花瓶”。最後呼叫display()方法列印最新的描述資訊。

在上述測試用例中,我們為porcelain物件添加了兩個裝飾品,最終的porcelain物件是Lily的物件,它是空瓷瓶+玫瑰+百合花的組合體。當最終該測試用例的Lily物件porcelain呼叫display()方法時,在display()方法中會呼叫該物件中的getDescription()方法,而該物件中的getDescription()方法會呼叫上一個修飾者(此處是Rose)物件的getDescription()方法,最終會找到我們的元件,也就是我們的空瓶子(Porcelain)中的的getDescription()方法。具體呼叫方式如下圖所示:

     

今天關於“裝飾者模式”的完整例項就先到這。