65.9K
CodeProject 正在变化。 阅读更多。
Home

装饰器设计模式的“如何”和“在哪里”

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (25投票s)

2015年2月4日

CPOL

5分钟阅读

viewsIcon

36447

downloadIcon

210

装饰器设计模式

引言

装饰器设计模式是 GOF 提出的行为模式之一。装饰器设计模式用于开发过程中,为现有类型提供额外功能。因此,装饰器设计模式允许开发人员实现 SOLID 原则。

  1. 单一职责原则 – 类/函数应该只做一件事,或者类/函数应该只有一个修改的原因。
  2. 开闭原则 – 类/函数对扩展开放,对修改关闭。
  3. 里氏替换原则 – 如果类型 S 是从类型 T 派生的,那么类型 T 的对象可以被类型 S 的对象替换。

装饰器意味着用装饰基础元素来实现额外功能。下图是装饰的一种展示。

 

 
基础礼物
礼品包装

图片展示了带有基础盒子的礼物,另一张图片是礼物上的精美包装装饰。这里实现的额外功能是,通过包装装饰,让基础礼物看起来更漂亮。

设计模式的简单示例

下图展示了基础装饰器设计模式的类图。

  • IBasicService - 需要由派生类型实现的基接口。
  • BasicServiceImplementation – 它是派生自接口的基础/具体实现,作为基础提供基本功能。
  • Decorator1OnBasic & Decorator2OnBasic – 它是派生自接口的装饰实现。它实际上在基本实现之上提供了额外功能。
  • Client – 使用具体实现,它创建 Decorate 的实例并使用其功能。

下面的代码是上面讨论的装饰器设计模式和类图的实现。

namespace BasicDecoratorPattern
{
    public interface IBaseService
    {
        void Print();
    }

    public class BasicServiceImplementaion : IBaseService
    {
        public void Print()
        {
            Console.WriteLine("Basic Item");
        }
    }

    public class Decorator1OnBasic : IBaseService
    {
        private readonly IBaseService BasicRealTimeService;
        public Decorator1OnBasic(IBaseService service)
        {
            BasicRealTimeService = service;
        }

        public void Print()
        {
            BasicRealTimeService.Print();
            Console.WriteLine("Extra functionality from Decorator ONE");
        }
    }

    public class Decorator2OnBasic : IBaseService
    {
        private readonly IBaseService BasicRealTimeService;
        public Decorator2OnBasic(IBaseService service)
        {
            BasicRealTimeService = service;
        }

        public void Print()
        {
            BasicRealTimeService.Print();
            Console.WriteLine("Extra functionality from Decorator SECOND");
        }
    }

    public class Client
    {
        public Client()
        {
            IBaseService realTimeService = new BasicServiceImplementaion();

            IBaseService basicRealTimeServiceDecorator1 = new Decorator1OnBasic(realTimeService);
            IBaseService basicRealTimeServiceDecorator2 = new Decorator2OnBasic(realTimeService);

            basicRealTimeServiceDecorator1.Print();
            basicRealTimeServiceDecorator2.Print();
        }
    }
}

在上面的代码实现中需要注意的点如下所列:

  1. DecoratorOnBasic 接收 IBasicService 实例作为输入,以创建 decorator 类的实例。
  2. 客户端首先创建 BasicServiceImplementation 的实例,然后将该实例传递给 decorator
  3. Decorator 利用传递给它的基本实现实例来实现基本功能,并通过装饰它来实现额外功能。

输出

 

输出显示装饰器在基本功能之上添加了额外功能。为了进一步理解,下面是装饰器模式在现实世界中的另一个例子。

设计模式的现实世界示例

下图是现实世界设计模式的类图。下面的类图代表了不同种类的 Milkshake Mango & Chocolate),它们都基于基础的 Milkshake

与基础实现的映射

  • IMilkShake 等同于 IBasicService
  • MilkShake 等同于 BasicImplementation
  • MangoMilkshake & ChoclateMilkShake 等同于 Decorator1OnBasic & Decorator2OnBasic

在此实现中请注意,MilkshakeDecorator 是一个 abstract 类,它派生自 IMilkShake ,而 MilkShake Decorator 派生自这个 decorator 类。有一些公共功能,因此创建了这个类,但它不影响装饰器模式的实际实现。

namespace RealWorldDecoratorPattern
{
    public interface IMilkShake
    {
        string Serve();
        int Price();
    }

    public class MilkShake : IMilkShake
    {
        public string Serve()
        {
            return "MilkShake";
        }

        public int Price()
        {
            return 30;
        }
    }

    public abstract class MilkshakeDecorator : IMilkShake
    {
        public readonly IMilkShake Milkshake;
        public MilkshakeDecorator(IMilkShake milkShake)
        {
            Milkshake = milkShake;
        }
        public string Flavour { get; set; }
        public int FlavourPrice { get; set; }

        public abstract string Serve();
        public abstract int Price();

    }

    public class MangoMilkShake : MilkshakeDecorator
    {
        public MangoMilkShake(IMilkShake milkShake)
            : base(milkShake)
        {
            this.Flavour = "Mango";
            this.FlavourPrice = 10; 
        }

        public override string Serve()
        {
            return  "Serving " + this.Flavour + " " + Milkshake.Serve();
        }

        public override int Price()
        {
            return  this.FlavourPrice  + Milkshake.Price();
        }
    }

    public class ChoclateMilkShake : MilkshakeDecorator
    {
        public ChoclateMilkShake(IMilkShake milkShake)
            : base(milkShake)
        {
            this.Flavour = "Choclate";
            this.FlavourPrice = 20;
        }

        public override string Serve()
        {
            return "Serving "  + this.Flavour + " " + Milkshake.Serve();
        }

        public override int Price()
        {
            return this.FlavourPrice + Milkshake.Price();
        }
    }

    public class Client
    {
        public Client()
        {
            IMilkShake milkShake = new MilkShake();

            IMilkShake mangoMilkshake = new MangoMilkShake(milkShake);
            IMilkShake choclateMilkshake = new ChoclateMilkShake(milkShake);

            Console.WriteLine(mangoMilkshake.Serve());
            Console.WriteLine(mangoMilkshake.Price());

            Console.WriteLine();

            Console.WriteLine(choclateMilkshake.Serve());
            Console.WriteLine(choclateMilkshake.Price());
        }
    }
}

输出

在上面的代码中,MilkShake Decorator 类(Mango Chocolate)使用了基础的 Mikshake 类。Decorator 类对基础 Milkshake 类进行装饰,并通过使用基本实现和额外功能来提供输出。

在应用程序中使用设计模式

以上两个示例有助于理解装饰器设计模式的 Basic RealWorld 问题。但本节旨在帮助您了解如何在应用程序中使用设计模式,即开发人员可能在应用程序中使用它的地方。

装饰器设计模式对于实现横切关注点/面向切面编程概念非常有用,例如:

  1. 身份验证
  2. Authorization
  3. 日志记录
  4. 缓存
  5. 验证
  6. 异常管理

除了前面解释的横切关注点之外,它还可以用于用额外的功能装饰类,也就是说,不总是只能使用装饰器模式来实现横切关注点。

下面是通过 CachingDecorator 实现缓存横切关注点的类图。

  • IProvides 等同于 IBasicService – 在这个例子中,它是具有 GetProviderList 的契约。
  • Provider 等同于 BasicServiceImplementation – 在这个例子中,它是具体实现,用于获取提供商列表。
  • CacheProvider 等同于 DecoratorOnBasic – 在这个例子中,它是进行缓存获取的提供商的任务的装饰器,当请求时,它提供缓存数据,或者如果缓存数据不可用,它会从 Provider(基础)实现请求新数据。
namespace CacheDecoratorPattern
{
    public interface IProviders
    {
        NameValueCollection GetProviderList();
    }

    public class Providers : IProviders
    {
        public NameValueCollection GetProviderList()
        {
            NameValueCollection providerList = new NameValueCollection();
            providerList.Add("SQL", "SQLProvider");
            providerList.Add("Oracle", "OracleProvider");
            providerList.Add("MySQL", "MyProvider");
            return providerList;
        }
    }

    public class CacheProvider : IProviders
    {
        private readonly IProviders provider;

        private NameValueCollection CachedProviderList;

        public CacheProvider(IProviders provider)
        {
            this.provider = provider;
        }

        public NameValueCollection GetProviderList()
        {
            if(CachedProviderList == null)
                CachedProviderList = provider.GetProviderList();

            return CachedProviderList;
        }
    }

    public class Client
    {
        public Client()
        {
            IProviders provider = new Providers();
            CacheProvider cacheProvider = new CacheProvider(provider);

            var providerlist = cacheProvider.GetProviderList();
        }
    }
}

在代码中,CacheProvider 是一个装饰 Provider 类的类,并将 IProvider 作为输入。由于这只是一个示例,目前缓存值存储在 CacheProvider private 变量中,但在实际应用中,这可以被真实的缓存替换,也就是说,它可以是 Web 应用程序缓存类或 Enterprise Application Library 缓存块。

与依赖注入容器结合的装饰器

下面是如何将装饰器实例注册到 Microsoft Unity 容器的示例代码。

注册装饰器

 var container = new UnityContainer();
  container.RegisterType(
      typeof( IProvider ),
      typeof( Provider ),
      "BasicProvider"
  );
  contract.RegisterType(
      typeof( IProvider ),
      typeof( CacheProvider ),
     new InjectionConstructor(
         new ResolvedParameter(
             typeof( IProvider ),
             "BasicProvider"
         )
     )
 );

因此,一旦它与容器的 Resolve 方法一起注册,就可以轻松获取 Decorator 的实例。

var contract = container.Resolve<IProvider>();

最终实现了 SOLID 原则

  • 单一职责原则 – 在示例中,基础实现 Provider 执行其负责的任务,例如:在最后一个示例中获取数据。而装饰器负责执行额外功能,例如 CacheProvider 负责缓存数据,而不是获取数据。
  • 开闭原则 - 正如这里的规则所述,基础实现 Provider 对修改关闭,对扩展开放,这通过 CacheProvider 实现,它扩展了基础实现的功能。
  • 里氏替换原则 – 正如这里的规则所述,基础实现 Provider 对象在 Decorator 构造函数中被父类型 IProvider 接口替换,其中 Provider 对象由客户端类注入。

注意

这是我对该模式的看法。请提供您的反馈,如果您发现此帖有任何错误,也请提供反馈。

© . All rights reserved.