溫故知新(1)——裝飾者模式


有些基礎知識很早就學習過了,但可能當時理解不深刻,或者工作中沒有應用到,以致漸漸淡忘了。這個系列就是對這些淡忘知識的一個復習,也希望復習的同時可以加深理解,故取名溫故知新。

概述

裝飾者模式是GOF23種設計模式的一個,屬於結構型的設計模式。主要意圖是:動態的給一個對象添加一些額外的職責。“動態”和“給一個對象”的表述說明了這種“添加額外職責”是在運行期決定的,而不是由靜態的父子類繼承實現。因此應用裝飾着模式提供了較大的靈活性,由組合替代了繼承,避免了子類的數量上的爆炸。

下面引用了一個論壇帖子(http://bbs.m.the9.com/forum.php?mod=viewthread&tid=700)中對裝飾者模式應用場景、優點與缺點的描述:

裝飾者模式的應用場景:
1、 想透明並且動態地給對象增加新的職責的時候。
2、 給對象增加的職責,在未來存在增加或減少可能。
3、 用繼承擴展功能不太現實的情況下,應該考慮用組合的方式。
裝飾者模式的優點:
1、 通過組合而非繼承的方式,實現了動態擴展對象的功能的能力。
2、 有效避免了使用繼承的方式擴展對象功能而帶來的靈活性差,子類無限制擴張的問題。
3、 充分利用了繼承和組合的長處和短處,在靈活性和擴展性之間找到完美的平衡點。
4、 裝飾者和被裝飾者之間雖然都是同一類型,但是它們彼此是完全獨立並可以各自獨立任意改變的。
5、 遵守大部分GRASP原則和常用設計原則,高內聚、低偶合。
裝飾者模式的缺點:
1、 裝飾鏈不能過長,否則會影響效率。
2、 因為所有對象都是Component,所以如果Component內部結構發生改變,則不可避免地影響所有子類(裝飾者和被裝飾者),也就是說,通過繼承建立的關系總是脆弱地,如果基類改變,勢必影響對象的內部,而通過組合(Decoator HAS A Component)建立的關系只會影響被裝飾對象的外部特征。
3、只在必要的時候使用裝飾者模式,否則會提高程序的復雜性,增加系統維護難度。

結構

裝飾者模式的實現類圖:

裝飾者

從圖中可以看出裝飾着模式包含如下參與者:

1、一個被包裝類和包裝類均需遵守的接口——IComponent;

2、被包裝類——ConcreteComponent;

3、包裝類的抽象類——Decorator;

4、包裝類的具體實現——DecoratorA、DecoratorB;

5、發起調用的客戶端程序——Client。

示例

示例的業務場景:某網頁上有一個產品列表的模塊,這個模塊展示一系列產品。后來隨着業務的發展,允許廠商付費推廣的產品,這些推廣的產品需要附加在普通產品的前面。

經過分析,產品的列表的產生邏輯是易於變化的部分,可能加入新的規則,也可能去掉舊的規則。這些規則構成了對原有對象的“包裝”,因此采用裝飾者模式進行實現。

下面代碼與上面類圖的對應關系已經用注釋標出:

1、首先定義一個表示產品的模型類Product。

 1:  using System;
 2:   
 3:  namespace DesignPatterns.Decorator
 4:  {
 5:      /// <summary>
 6:      /// 商品類
 7:      /// </summary>
 8:      public class Product
 9:      {
10:          public int Id { get; set; }
11:          public string Name { get; set; }
12:      }
13:  }
14:   

2、IProductsBlock接口。

 1:  using System;
 2:  using System.Collections.Generic;
 3:   
 4:  namespace DesignPatterns.Decorator
 5:  {
 6:      /// <summary>
 7:      /// 產品塊接口-被包裝對象和包裝對象均實現此接口
 8:      /// </summary>
 9:      public interface IProductsBlock
10:      {
11:          List<Product> GetProductsBlock();
12:      }
13:  }
14:   

3、基本商品塊ProductsBlock。

 1:  using System;
 2:  using System.Collections.Generic;
 3:   
 4:  namespace DesignPatterns.Decorator
 5:  {
 6:      /// <summary>
 7:      /// 基本商品塊-被包裝的基礎對象
 8:      /// </summary>
 9:      public class ProductsBlock : IProductsBlock
10:      {
11:          public List<Product> GetProductsBlock()
12:          {
13:              List<Product> products = new List<Product>() {
14:                  new Product() { Id = 11, Name = "一般商品1" },
15:                  new Product() { Id = 12, Name = "一般商品2" },
16:                  new Product() { Id = 13, Name = "一般商品3" }
17:              };
18:   
19:              return products;
20:          }
21:      }
22:  }
23:   

4、包裝類的抽象類BlockDecorator。

 1:  using System;
 2:  using System.Collections.Generic;
 3:   
 4:  namespace DesignPatterns.Decorator
 5:  {
 6:      /// <summary>
 7:      /// 包裝類的抽象父類
 8:      /// </summary>
 9:      public abstract class BlockDecorator : IProductsBlock
10:      {
11:          protected IProductsBlock block;
12:   
13:          public BlockDecorator(IProductsBlock block)
14:          {
15:              this.block = block;
16:          }
17:   
18:          public abstract List<Product> GetProductsBlock();
19:      }
20:  }
21:   

5、附加廣告商品的包裝器實現類AdDecorator。

 1:  using System;
 2:  using System.Collections.Generic;
 3:   
 4:  namespace DesignPatterns.Decorator
 5:  {
 6:      /// <summary>
 7:      /// 附加廣告商品的包裝器實現
 8:      /// </summary>
 9:      public class AdDecorator : BlockDecorator
10:      {
11:          public AdDecorator(IProductsBlock block)
12:              : base(block)
13:          { }
14:   
15:          public override List<Product> GetProductsBlock()
16:          {
17:              List<Product> adProducts = new List<Product>() {
18:                  new Product() { Id = 11, Name = "廣告商品1" },
19:                  new Product() { Id = 12, Name = "廣告商品2" }
20:              };
21:              var list = this.block.GetProductsBlock();
22:              list.InsertRange(0, adProducts);
23:   
24:              return list;
25:          }
26:      }
27:  }
28:   

6、最后完成客戶端代碼。

 1:  using System;
 2:  using System.Collections.Generic;
 3:   
 4:  namespace DesignPatterns.Decorator
 5:  {
 6:      class Program
 7:      {
 8:          static void Main(string[] args)
 9:          {
10:              //組裝過程
11:              IProductsBlock block = new ProductsBlock();
12:              block = new AdDecorator(block);
13:   
14:              //對客戶程序來說,包裝是透明的
15:              var products = block.GetProductsBlock();
16:   
17:              foreach (var p in products)
18:              {
19:                  Console.WriteLine(p.Name);
20:              }
21:              Console.WriteLine("按任意鍵結束...");
22:              Console.ReadKey();
23:          }
24:      }
25:  }
26:   

7、查看結果。可以看到廣告產品已經插入到一般產品前面了。

image

變化出現了,假設為了吸引用戶,需要在廣告產品和一般產品之間插入一些降價產品,應該怎么實現呢?

8、增加一個包裝類的實現CutPriceDecorator。

 1:  using System;
 2:  using System.Collections.Generic;
 3:   
 4:  namespace DesignPatterns.Decorator
 5:  {
 6:      /// <summary>
 7:      /// 附加降價商品的包裝類實現
 8:      /// </summary>
 9:      public class CutPriceDecorator : BlockDecorator
10:      {
11:          public CutPriceDecorator(IProductsBlock block)
12:              : base(block)
13:          { }
14:   
15:          public override List<Product> GetProductsBlock()
16:          {
17:              List<Product> adProducts = new List<Product>() {
18:                  new Product() { Id = 21, Name = "降價商品1" },
19:                  new Product() { Id = 22, Name = "降價商品2" }
20:              };
21:              var list = this.block.GetProductsBlock();
22:              list.InsertRange(0, adProducts);
23:   
24:              return list;
25:          }
26:      }
27:  }
28:   

9、稍微修改一下客戶端程序中的組裝過程。(第12行)

 1:  using System;
 2:  using System.Collections.Generic;
 3:   
 4:  namespace DesignPatterns.Decorator
 5:  {
 6:      class Program
 7:      {
 8:          static void Main(string[] args)
 9:          {
10:              //組裝過程
11:              IProductsBlock block = new ProductsBlock();
12:              block = new CutPriceDecorator(block); //添加降價商品
13:              block = new AdDecorator(block);
14:   
15:              //對客戶程序來說,包裝是透明的
16:              var products = block.GetProductsBlock();
17:   
18:              foreach (var p in products)
19:              {
20:                  Console.WriteLine(p.Name);
21:              }
22:              Console.WriteLine("按任意鍵結束...");
23:              Console.ReadKey();
24:          }
25:      }
26:  }
27:   

10、再次查看運行結果。降價產品已經插入到結果中了。

image

通過代碼可以發現包裝器的組裝是有次序的,次序不同結果可能也不相同。所以可以通過建立不同的包裝類,調整包裝類不同的組裝順序,為基礎對象附加不同的職責。


免責聲明!

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



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