2012年8月31日星期五

温故知新装饰者模式

温故知新装饰者模式

有些基础知识很早就学习过了,但可能当时理解不深刻,或者工作中没有应用到,以致渐渐淡忘了。这个系列就是对这些淡忘知识的一个复习,也希望复习的同时可以加深理解,故取名温故知新。

概述

装饰者模式是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

通过代码可以发现包装器的组装是有次序的,次序不同结果可能也不相同。所以可以通过建立不同的包装类,调整包装类不同的组装顺序,为基础对象附加不同的职责。


TAG: