裝飾者模式其實有點難以理解,特別是對初學者來講可能有點暈,由於它的概念互相沖突,哪裏互相沖突咱們下面會講解到。編程
本人保持一向的寫做風格,重在入門。在本人的這篇文章中會用一個比較恰當的比喻來讓咱們對問題迎刃而解,例子雖然簡單可是重點突出。設計模式
在寫這篇文章以前我在網上大概搜了一下關於「裝飾者模式」的一些文章,可是講解的效果都不太理想。要麼就是找書搬過來的,要麼就是對着書的例子重新創造一個。我看了大概三四篇這樣子,不行看着頭暈。文章的主人很想把問題的關鍵說清楚,可是不多能在原有代碼的基礎上畫龍點睛,搞很差就是多此一舉。若是沒能清楚的介紹模式代碼中的任何一腳,都有可能給看文章的初學者帶來新的問題,沒可以透徹的體現出文章的重點。下面咱們從理清頭緒開始。[王清培版權全部,轉載請給出署名]架構
設計模式是用來解決某一個問題的一個方法,一個模式是對應着一個問題,好比觀察者模式就是用來解決一對多的關係,這種關係是「牽一髮而動全身」的做用。ide
咱們所看的設計模式書籍是一系列問題的集合,也是設計模式的集合。在咱們尚未能力將他們融會貫通以前,先單獨理解這些思想。當咱們能駕馭這些設計模式以後,咱們就可以設計出不錯的系統架構。模式之間是相通的,「設計原則」是引導模式創新的根本。書上的模式多數都是用來考慮一些小例子而已,若是用在真正的項目中,就須要結合整個設計模式的運用了。因此當咱們學習一些小的設計模式時,咱們不牽扯到其餘的多餘東西,先理解咱們當前模式的真正的思想是什麼。學習
裝飾者模式定義:動態將職責附加到對象上,若要擴展功能,裝飾者提供了比繼承更具彈性的代替方案;spa
這是裝飾者模式比較官方的定義,這句話咱們基本上都能理解其含義是什麼。無非是動態的給要擴展的對象加上功能,不用繼承而是用組合的方式。就是這句話給咱們初學者帶來了第一個問題,是用組合而不是用繼承來擴展對象的功能。咱們第一次接觸裝飾者模式的時候,就盯住了這句話,就是由於咱們盯住了這句話,因此咱們在下面的思考過程當中老是帶着這個理論,因此老是會理解不了。朋友先不要記這個理論,先拋開不要記任何理論模型,我會用一個比喻來逐漸的讓你理解裝飾者模式真正的含義是什麼。設計
請進入個人學習模式,在這裏我打一個比喻;假如我家裏如今要裝修,要裝修一個天花板上的燈。你們都知道天花板上的燈都是須要燈具進行裝飾的,在這裏咱們已經引入到了裝飾的概念了,好咱們再來分析問題。那麼燈具裏面的燈泡是不變的,咱們又引入了以關鍵的概念,就是被裝飾對象。燈泡是咱們一裝修的時候就有的,外面的燈具是隨時能夠更換的,這裏就造成了典型的裝飾者模式的原型。請看圖:[王清培版權全部,轉載請給出署名]3d
1:對象
裏面的燈泡就是被裝飾者,外面的燈具就是裝飾者。咱們已基本認識了裝飾者模式的含義是什麼了,下面咱們就用代碼來進行模擬裝飾者模式。blog
燈泡代碼:
- using System;
- using System.Collections.Generic;
- using System.Text;
- namespace ConsoleApplication1
- {
- public class 燈泡
- {
- public int 燈泡型號
- {
- get
- {
- return 10;
- }
- }
- public string 點亮燈泡()
- {
- return "燈泡已經點亮";
- }
- }
- }
不要問爲何燈泡類不是抽象的,在這裏咱們不討論其餘的原理,咱們先理清裝飾者模式在說,後面我會慢慢引入大家所迷惑的概念。
這是燈泡代碼,裏面很簡單,一個表示燈泡型號的屬性和一個點亮燈泡的方法。下面咱們來看裝飾燈泡的燈具代碼。
矩形燈具代碼:
- using System;
- using System.Collections.Generic;
- using System.Text;
- namespace ConsoleApplication1
- {
- public class 矩形燈具
- {
- /// <summary>
- /// 燈泡的引用
- /// </summary>
- private 燈泡 dengpaoobject;
- /// <summary>
- /// 既然是燈具裝飾,那麼必須能容納燈泡,因此燈具必須引用燈泡
- /// </summary>
- /// <param name="d">燈泡的實例</param>
- public void 添加裝飾的燈泡(燈泡 d)
- {
- dengpaoobject = d;
- }
- /// <summary>
- /// 打開裝飾了矩形燈具的感受
- /// </summary>
- /// <returns>添加裝飾以後的效果</returns>
- public string 打開矩形燈具的效果()
- {
- return dengpaoobject.點亮燈泡() + ",矩形燈具所發出的光";
- }
- }
- }
多邊形燈具代碼:
- using System;
- using System.Collections.Generic;
- using System.Text;
- namespace ConsoleApplication1
- {
- public class 多邊形燈具
- {
- /// <summary>
- /// 燈泡的引用
- /// </summary>
- private 燈泡 dengpaoobject;
- /// <summary>
- /// 既然是燈具裝飾,那麼必須能容納燈泡,因此燈具必須引用燈泡
- /// </summary>
- /// <param name="d">燈泡的實例</param>
- public void 添加裝飾的燈泡(燈泡 d)
- {
- dengpaoobject = d;
- }
- /// <summary>
- /// 打開裝飾了矩形燈具的感受
- /// </summary>
- /// <returns>添加裝飾以後的效果</returns>
- public string 打開多邊形燈具的效果()
- {
- return dengpaoobject.點亮燈泡() + ",多邊形燈具所發出的光";
- }
- }
- }
矩形陰影燈具:
- using System;
- using System.Collections.Generic;
- using System.Text;
- namespace ConsoleApplication1
- {
- public class 矩形陰影燈具
- {
- /// <summary>
- /// 燈泡的引用
- /// </summary>
- private 燈泡 dengpaoobject;
- /// <summary>
- /// 既然是燈具裝飾,那麼必須能容納燈泡,因此燈具必須引用燈泡
- /// </summary>
- /// <param name="d">燈泡的實例</param>
- public void 添加裝飾的燈泡(燈泡 d)
- {
- dengpaoobject = d;
- }
- /// <summary>
- /// 打開裝飾了矩形燈具的感受
- /// </summary>
- /// <returns>添加裝飾以後的效果</returns>
- public string 打開多邊形燈具的效果()
- {
- return dengpaoobject.點亮燈泡() + ",矩形陰影燈具所發出的光";
- }
- }
- }
不要問爲何裝飾燈具沒有繼承上面的燈泡,我之因此這樣講解,就是爲了繞開那個給咱們帶來理解上困惑的陷阱。繼續往下看就能完全明白了。
裝飾者跟被裝飾者之間沒有任何繼承關係,「裝飾者模式」官方的解釋就是這個意思,只是經過組合來擴展對象的職責。若是正常狀況下,咱們確定是用繼承來解決燈具的裝飾問題,有多少了燈具都繼承自燈泡類,可是經過這種包含的方式就繞開了繼承帶來的耦合問題。上面的代碼還遠遠沒有完成,咱們來看後面會出現什麼樣的問題。
模式燈具的效果:
- using System;
- using System.Collections.Generic;
- using System.Text;
- namespace ConsoleApplication1
- {
- class Program
- {
- static void Main(string[] args)
- {
- 燈泡 dengpao = new 燈泡();
- 矩形燈具 juxingdengju = new 矩形燈具();
- juxingdengju.添加裝飾的燈泡(dengpao);
- Console.WriteLine(juxingdengju.打開矩形燈具的效果());
- Console.ReadLine();
- 多邊形燈具 duobianxingdengju = new 多邊形燈具();
- duobianxingdengju.添加裝飾的燈泡(dengpao);
- Console.WriteLine(duobianxingdengju.打開多邊形燈具的效果());
- Console.ReadLine();
- 矩形陰影燈具 yinyingdengju = new 矩形陰影燈具();
- yinyingdengju.添加裝飾的燈泡(dengpao);
- Console.WriteLine(yinyingdengju.打開多邊形燈具的效果());
- Console.ReadLine();
- }
- }
- }
2:
從上面的燈具例子來看,咱們基本上完成了裝飾者模式的模擬。可是這樣的代碼擴展性太差,設計模式所提倡面向接口編程,而咱們這裏彷佛沒有看見接口。例子中的全部代碼都是直接使用對象的名稱,這樣確定是耦合的。咱們繼續延伸問題,來解決你剛開始開文章的困境,抽象類、接口、繼承跑哪去了。總以爲例子的代碼太簡單了,哪像是設計模式啊。別急咱們繼續討論,設計模式原本就是思想性的理論,須要耐心的研究。
咱們的燈泡是實體類而不是抽象類,這就說明咱們的燈泡設計是不合理的,咱們假如燈泡是個半成品,這個半成品是不能直接用的,任何裝飾者都須要通過必定的修改才能使用。因此這樣一來,咱們的燈泡類就是抽象的了;
那爲何須要用裝飾者來繼承被裝飾者呢,其實很簡單緣由就是沒有統一的接口。咱們假如燈泡只能用一種方式打開,任何燈具都不能擅自修改這統一的接口。由於這接口是燈泡廠商規定的,好比一些電流、電壓、線路原理等等都是不容許直接修改的。只有繼承自裝飾者對象我纔可以使用被裝飾者的約定,好比一個方法的名稱、一個屬性的名稱。而咱們在使用的時候徹底是使用被裝飾者的方式在使用,這樣就破快了約定。
還有就是爲何咱們沒有用接口,咱們來延伸出接口的使用。假如一個燈具能夠裝飾不少種燈泡,那麼必然就須要一個統一的接口來約束這些必備條件。 好比燈具只能裝飾多大的燈泡、什麼型號的燈泡等等;
上面的幾個問題,我就大概的描述了一下。下面咱們來對代碼進行一些修改,讓它看起來像「裝飾者模式」。
更改後的燈泡代碼:
- using System;
- using System.Collections.Generic;
- using System.Text;
- namespace ConsoleApplication1
- {
- public interface 燈泡系列
- {
- int 燈泡型號 { get; }
- string 打開燈();
- }
- public abstract class 燈泡 : 燈泡系列
- {
- public int 燈泡型號
- {
- get { return 10; }
- }
- public virtual string 打開燈()
- {
- return "燈泡已經點亮";
- }
- }
- public class 紅色燈泡 : 燈泡
- {
- public int 燈泡型號
- {
- get { return 10; }
- }
- public override string 打開燈()
- {
- return "紅色燈泡";
- }
- }
- }
更改後的矩形燈具代碼:
- using System;
- using System.Collections.Generic;
- using System.Text;
- namespace ConsoleApplication1
- {
- public class 矩形燈具 : 燈泡
- {
- /// <summary>
- /// 燈泡的引用
- /// </summary>
- private 燈泡系列 dengpaoobject;
- /// <summary>
- /// 既然是燈具裝飾,那麼必須能容納燈泡,因此燈具必須引用燈泡
- /// </summary>
- /// <param name="d">燈泡的實例</param>
- public void 添加裝飾的燈泡(燈泡 d)
- {
- dengpaoobject = d;
- }
- public override string 打開燈()
- {
- return dengpaoobject.打開燈() + ",外加矩形燈具效果";
- }
- }
- }
其餘幾個燈具代碼都同樣的我就不貼出來了。
調用代碼:
- using System;
- using System.Collections.Generic;
- using System.Text;
- namespace ConsoleApplication1
- {
- class Program
- {
- static void Main(string[] args)
- {
- 紅色燈泡 reddengpao = new 紅色燈泡();//燈泡
- 矩形燈具 juxingdengju = new 矩形燈具();//裝飾
- 多邊形燈具 duobianxingdengju = new 多邊形燈具();//裝飾
- duobianxingdengju.添加裝飾的燈泡(reddengpao);
- juxingdengju.添加裝飾的燈泡(duobianxingdengju);
- Console.WriteLine(juxingdengju.打開燈());
- Console.ReadLine();
- }
- }
- }
3:
裝飾者模式就講的差很少了。這裏面用到了接口、繼承、多態等特性,其實接口就是用來消除類之間的耦合,繼承是爲了拿對象的行爲,多態是爲了將職責動態的添加到燈具中去。模式中的一些特性是會隨着環境的不一樣而不一樣,有些時候根本不須要接口,可是爲了進行多類型之間的擴展就必須進行接口編程。
總結:我我的以爲裝飾者模式用的場合不是不少,繼承就是爲了拿對象的行爲,而後在單獨引用一個對象的實例,這樣就等於浪費了一個對象的內存。簡單的裝飾者能夠不用繼承,若是須要統一調用的話就須要繼承了,接口只是用來表示裝飾者不只僅能夠裝飾某一個對象,而是某一類對象。根據須要的不一樣模式能夠適當的進行修改,以適應當前環境。