深入理解設計模式(19):裝飾模式


一、前言

裝飾模式實際上是一直提倡的組合代替繼承的實踐方式,個人認為要理解裝飾者模式首先需要理解為什么需要組合代替繼承,繼承又是為什么讓人深惡痛絕.

為什么建議使用組合代替繼承?

面向對象的特性有繼承與封裝,但兩者卻又有一點矛盾,繼承意味子類依賴了父類中的實現,一旦父類中改變實現則會對子類造成影響,這是打破了封裝性的一種表現. 而組合就是巧用封裝性來實現繼承功能的代碼復用.

二、什么是裝飾模式

1.定義:

裝飾器模式又名包裝(Wrapper)模式。裝飾器模式以對客戶端透明的方式拓展對象的功能,是繼承關系的一種替代方案。

2.意圖

動態地給一個對象添加一些額外的職責。就增加功能來說,Decorator模式相比生成子類更為靈活。

3.別名

包裝器Wrapper

4.動機

有時我們希望給某個對象而不是整個類添加一些功能。例如,一個圖形用戶界面工具箱允許你對任意一個用戶界面組件添加一些組件,例如邊框,或是一些行為,例如窗口滾動等。

5.作用

在不修改原有的接口的情況下,讓類表現的更好。

6.問題

自然是繼承有一些問題
繼承會導致超類和子類之間存在強耦合性,當超類改變時,子類也會隨之改變; 
超類的內部細節對於子類是可見的,繼承常常被認為破壞了封裝性; 

三、裝飾模式的結構

 

在裝飾器模式中的角色有:

  • 抽象構件(Component)角色:給出一個抽象接口,已規范准備接收附加責任的對象。
  • 具體構件(ConcreteComponent)角色:定義一個將要接收附加責任的類
  • 裝飾(Decorator)角色:持有一個構件(Component)對象的實例,並定義一個與抽象構件接口一致的接口。
  • 具體裝飾(ConcreteDecorator)角色:負責給構件對象“貼上”附加的責任。

四、裝飾模式的使用場景

  1. 需要擴展一個類的功能或給一個類增加附加責任。

  2. 需要動態地給一個對象增加功能,這些功能可以再動態地撤銷。

  3. 需要增加由一些基本功能的排列組合而產生的非常大量的功能

五、裝飾模式的優缺點

優點:

  1. 裝飾這模式和繼承的目的都是擴展對象的功能,但裝飾者模式比繼承更靈活

  2. 通過使用不同的具體裝飾類以及這些類的排列組合,設計師可以創造出很多不同行為的組合

  3. 裝飾者模式有很好地可擴展性

缺點

  裝飾者模式會導致設計中出現許多小對象,如果過度使用,會讓程序變的更復雜。並且更多的對象會是的差錯變得困難,特別是這些對象看上去都很像。

六、裝飾模式的實現

/// <summary>
    /// 手機抽象類,即裝飾者模式中的抽象組件類
    /// </summary>
    public abstract class Phone
    {
        public abstract void Print();
    }

    /// <summary>
    /// 蘋果手機,即裝飾着模式中的具體組件類
    /// </summary>
    public class ApplePhone:Phone
    {
        /// <summary>
        /// 重寫基類方法
        /// </summary>
        public override void Print()
        {
            Console.WriteLine("開始執行具體的對象——蘋果手機");
        }
    }

    /// <summary>
    /// 裝飾抽象類,要讓裝飾完全取代抽象組件,所以必須繼承自Photo
    /// </summary>
    public abstract class Decorator:Phone
    {
        private Phone phone;

        public Decorator(Phone p)
        {
            this.phone = p;
        }

        public override void Print()
        {
            if (phone != null)
            {
                phone.Print();
            }
        }
    }

    /// <summary>
    /// 貼膜,即具體裝飾者
    /// </summary>
    public class Sticker : Decorator
    {
        public Sticker(Phone p)
            : base(p)
        { 
        }

        public override void Print()
        {
            base.Print();

            // 添加新的行為
            AddSticker();      
        }

        /// <summary>
        /// 新的行為方法
        /// </summary>
        public void AddSticker()
        {
            Console.WriteLine("現在蘋果手機有貼膜了");
        }
    }

    /// <summary>
    /// 手機掛件
    /// </summary>
    public class Accessories : Decorator
    {
        public Accessories(Phone p)
            : base(p)
        {
        }

        public override void Print()
        {
            base.Print();

            // 添加新的行為
            AddAccessories();          
        }

        /// <summary>
        /// 新的行為方法
        /// </summary>
        public void AddAccessories()
        {
            Console.WriteLine("現在蘋果手機有漂亮的掛件了");
        }
    }

客戶端代碼

class Customer
    {
        static void Main(string[] args)
        {
            // 我買了個蘋果手機
            Phone phone = new ApplePhone();

            // 現在想貼膜了
            Decorator applePhoneWithSticker = new Sticker(phone);
            // 擴展貼膜行為
            applePhoneWithSticker.Print();
            Console.WriteLine("----------------------\n");

            // 現在我想有掛件了
            Decorator applePhoneWithAccessories = new Accessories(phone);
            // 擴展手機掛件行為
            applePhoneWithAccessories.Print();
            Console.WriteLine("----------------------\n");

            // 現在我同時有貼膜和手機掛件了
            Sticker sticker = new Sticker(phone);
            Accessories applePhoneWithAccessoriesAndSticker = new Accessories(sticker);
            applePhoneWithAccessoriesAndSticker.Print();
            Console.ReadLine();
        }

從上面的客戶端代碼可以看出,客戶端可以動態地將手機配件增加到手機上,如果需要添加手機外殼時,此時只需要添加一個繼承Decorator的手機外殼類,從而,裝飾模式擴展性也非常好。

七、裝飾模式的.NET應用

在.NET 類庫中也有裝飾者模式的實現,該類就是System.IO.Stream

MemoryStream memoryStream = new MemoryStream(new byte[] {95,96,97,98,99});

            // 擴展緩沖的功能
            BufferedStream buffStream = new BufferedStream(memoryStream);

            // 添加加密的功能
            CryptoStream cryptoStream = new CryptoStream(memoryStream,new AesManaged().CreateEncryptor(),CryptoStreamMode.Write);
            // 添加壓縮功能
            GZipStream gzipStream = new GZipStream(memoryStream, CompressionMode.Compress, true);

八、總結

裝飾者模式本質上來說是AOP思想的一種實現方式,其持有被裝飾者,因此可以控制被裝飾者的行為從而達到了AOP的效果。

要點:
1:繼承屬於擴展形式一種,但不見的是達到彈性設計的最佳方式,組合優於繼承。
2:應該允許行為可以被拓展,而無需修改現有的代碼。
3:裝飾者模式意味着一群裝飾者類,這些類用來包裝具體組件。
4:裝飾者類反映出被裝飾組件類型。
5:可以使用無數個裝飾者包裝一個組件。
6:裝飾者會導致設計中出現許多小對象,如果過度使用,會讓程序變得很復雜。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM