Head First设计模式之装饰者模式

简介: 一、定义 装饰者模式,英文叫Decorator Pattern,在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。 动态将职责附加到对象上,若要扩展功能,装饰者提供了比继承更具弹性的代替方案。

一、定义

装饰者模式,英文叫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设计模式之目录

 

相关文章
|
4月前
|
设计模式 Java
Java设计模式【十】:装饰者模式
Java设计模式【十】:装饰者模式
22 0
|
2月前
|
设计模式 缓存 安全
设计模式-代理模式(静态代理、动态代理、cglib代理)、代理模式和装饰者模式的区别
设计模式-代理模式(静态代理、动态代理、cglib代理)、代理模式和装饰者模式的区别
55 1
|
3月前
|
设计模式 Go 开发工具
Golang设计模式——22装饰者模式
Golang设计模式——22装饰者模式
22 0
|
4月前
|
设计模式 Java
根据真实业务场景去实现一下设计模式中的装饰者模式
根据真实业务场景去实现一下设计模式中的装饰者模式
18 0
|
4月前
|
设计模式 人工智能 移动开发
认真学习设计模式之装饰者模式(Decorator Pattern)
写在前言 利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。通过动态地组合对象,可以写新的代码添加新功能,而无须修改现有代码。既然没有改变现有代码,那么引进bug或产生意外副作用的机会将大幅度减少。
16 0
|
7月前
|
设计模式
设计模式~~~装饰者模式
设计模式~~~装饰者模式
21 0
|
8月前
|
设计模式 Java
java实现23种设计模式-装饰者模式
java实现23种设计模式-装饰者模式
79 0
|
16天前
|
设计模式 SQL 算法
设计模式了解哪些,模版模式
设计模式了解哪些,模版模式
19 0
|
1月前
|
设计模式 Java uml
C++设计模式之 依赖注入模式探索
C++设计模式之 依赖注入模式探索
37 0
|
2月前
|
设计模式 前端开发 JavaScript
观察者模式 vs 发布-订阅模式:两种设计模式的对决!
欢迎来到前端入门之旅!这个专栏是为那些对Web开发感兴趣、刚刚开始学习前端的读者们打造的。无论你是初学者还是有一些基础的开发者,我们都会在这里为你提供一个系统而又亲切的学习平台。我们以问答形式更新,为大家呈现精选的前端知识点和最佳实践。通过深入浅出的解释概念,并提供实际案例和练习,让你逐步建立起一个扎实的基础。无论是HTML、CSS、JavaScript还是最新的前端框架和工具,我们都将为你提供丰富的内容和实用技巧,帮助你更好地理解并运用前端开发中的各种技术。