一、定义
装饰者模式,英文叫Decorator Pattern,在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
动态将职责附加到对象上,若要扩展功能,装饰者提供了比继承更具弹性的代替方案。
设计原则:
1. 多用组合,少用继承。
利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。
2. 类应设计的对扩展开放,对修改关闭。
要点:
1. 装饰者和被装饰对象有相同的超类型。
2. 可以用一个或多个装饰者包装一个对象。
3. 装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为,以达到特定的目的。
4. 对象可以在任何时候被装饰,所以可以在运行时动态的,不限量的用你喜欢的装饰者来装饰对象。
5. 装饰模式中使用继承的关键是想达到装饰者和被装饰对象的类型匹配,而不是获得其行为。
6. 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。在实际项目中可以根据需要为装饰者添加新的行为,做到“半透明”装饰者。
7. 适配器模式的用意是改变对象的接口而不一定改变对象的性能,而装饰模式的用意是保持接口并增加对象的职责
二、结构
装饰者模式的实现类图:
从图中可以看出装饰着模式包含如下参与者:
1、一个被包装类和包装类均需遵守的接口——IComponent;
2、被包装类——ConcreteComponent;
3、包装类的抽象类——Decorator;
4、包装类的具体实现——DecoratorA、DecoratorB;
5、发起调用的客户端程序——Client。
三、实现
产品类
/// <summary> /// 商品类 /// </summary> public class Product { public int Id { get; set; } public string Name { get; set; } }
产品块
/// <summary> /// 产品块接口-被包装对象和包装对象均实现此接口 /// </summary> public interface IProductsBlock { List<Product> GetProductsBlock(); } /// <summary> /// 基本商品块-被包装的基础对象 /// </summary> public class ProductsBlock : IProductsBlock { public List<Product> GetProductsBlock() { List<Product> products = new List<Product>() { new Product() { Id = 11, Name = "一般商品1" }, new Product() { Id = 12, Name = "一般商品2" }, new Product() { Id = 13, Name = "一般商品3" } }; return products; } } /// <summary> /// 包装类的抽象父类 /// </summary> public abstract class BlockDecorator : IProductsBlock { protected IProductsBlock block; public BlockDecorator(IProductsBlock block) { this.block = block; } public abstract List<Product> GetProductsBlock(); } /// <summary> /// 附加广告商品的包装器实现 /// </summary> public class AdDecorator : BlockDecorator { public AdDecorator(IProductsBlock block) : base(block) { } public override List<Product> GetProductsBlock() { List<Product> adProducts = new List<Product>(){ new Product() { Id = 11, Name = "广告商品1" }, new Product() { Id = 12, Name = "广告商品2" } }; var list = this.block.GetProductsBlock(); list.InsertRange(0, adProducts); return list; } } /// <summary> /// 附加降价商品的包装类实现 /// </summary> public class CutPriceDecorator : BlockDecorator { public CutPriceDecorator(IProductsBlock block) : base(block) { } public override List<Product> GetProductsBlock() { List<Product> adProducts = new List<Product>() { new Product() { Id = 21, Name = "降价商品1" }, new Product() { Id = 22, Name = "降价商品2" } }; var list = this.block.GetProductsBlock(); list.InsertRange(0, adProducts); return list; } }
调用
//组装过程 IProductsBlock block = new ProductsBlock(); block = new CutPriceDecorator(block);//后期新增的降价商品 block = new AdDecorator(block); //对客户程序来说,包装是透明的 var products = block.GetProductsBlock(); foreach (var p in products) { Console.WriteLine(p.Name); } Console.WriteLine("按任意键结束..."); Console.ReadKey();
四、适用场景
1、 想透明并且动态地给对象增加新的职责的时候。
2、 给对象增加的职责,在未来存在增加或减少可能。
3、 用继承扩展功能不太现实的情况下,应该考虑用组合的方式。
五、优缺点
装饰者模式的优点:
1、 通过组合而非继承的方式,实现了动态扩展对象的功能的能力。
2、 有效避免了使用继承的方式扩展对象功能而带来的灵活性差,子类无限制扩张的问题。
3、 充分利用了继承和组合的长处和短处,在灵活性和扩展性之间找到完美的平衡点。
4、 装饰者和被装饰者之间虽然都是同一类型,但是它们彼此是完全独立并可以各自独立任意改变的。
5、 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。
装饰者模式的缺点:
1、 装饰链不能过长,否则会影响效率。
2、 因为所有对象都是Component,所以如果Component内部结构发生改变,则不可避免地影响所有子类(装饰者和被装饰者),也就是说,通过继承建立的关系总是脆弱地,如果基类改变,势必影响对象的内部,而通过组合(Decoator HAS A Component)建立的关系只会影响被装饰对象的外部特征。
3、只在必要的时候使用装饰者模式,否则会提高程序的复杂性,增加系统维护难度。
六、.NET中装饰者模式的实现
在.NET 类库中也有装饰者模式的实现,该类就是System.IO.Stream,下面看看Stream类结构:
上图中,BufferedStream、CryptoStream和GZipStream其实就是两个具体装饰类,这里的装饰者模式省略了抽象装饰角色(Decorator)。下面演示下客户端如何动态地为MemoryStream动态增加功能的。
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);
装饰者模式采用对象组合而非继承的方式实现了再运行时动态地扩展对象功能的能力,而且可以根据需要扩展多个功能,避免了单独使用继承带来的 ”灵活性差“和”多子类衍生问题“。
同时它很好地符合面向对象设计原则中 ”优先使用对象组合而非继承“和”开放-封闭“原则。
主要参考文章:http://www.cnblogs.com/zdy_bit/archive/2012/08/31/2665716.html
欢迎阅读本系列文章:Head First设计模式之目录