实战设计模式之装饰器模式

打印 上一主题 下一主题

主题 1070|帖子 1070|积分 3210

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
概述

        在软件开发中,我们经常碰到需要给现有对象添加新功能的情况。最直接的方法是通过继续来实现,即创建一个子类,并重写或新增方法。然而,继续这种方式有如下几个缺点。
        1、违反开放封闭原则。开放封闭原则指出软件实体应该是对扩展开放的,但是对修改关闭的。使用继续来添加功能会使得我们必须修改已有的类,这显然不符合这一原则。
        2、代码膨胀。如果一个类有多种可选的举动,那么为了覆盖所有大概的组合,大概会导致大量的子类产生,使代码库变得痴肥、难以维护。
        3、机动性不敷。一旦子类被创建出来,其举动就被固定下来了,无法在运行时动态地改变举动。
        相比之下,装饰器模式提供了更好的解决方案,由于它允许我们在不改变原对象的情况下,通过包装的方式为其添加新的举动。而且,这些举动可以在步伐运行期间动态地添加或移除,从而进步了代码的机动性和复用性。
        咖啡店点单是运用装饰器模式的一个范例例子:如果我们在咖啡店里点了一杯美式咖啡,然后根据自己的口味选择添加牛奶、糖、奶油等调料;每加一种调料,咖啡就变得更丰富,但基本的美式咖啡并没有改变。我们可以随时决定是否需要这些额外的配料,而且可以机动组合不同的配料以满足个人喜好。


基本原理

        装饰器模式的核心头脑是:提供一种动态地给对象添加职责的方式,而不是通过继续来静态地扩展功能。它允许举动和责任的组合在运行时进行,而且不会影响其他对象。装饰器模式特殊夸大了开放封闭原则,即软件实体应该对扩展开放,对修改关闭。装饰器模式包括如下四个核心组件。
        组件:界说了一个接口,所有的详细组件和装饰器都实现了这个接口。
        详细组件:实现了组件接口,界说了基本的举动和状态。
        装饰器:也实现了组件接口,但持有一个指向其他组件的引用。装饰器可以在调用被包装的对象之前或之后实行额外的操作,从而实现对原始举动的增强。
        详细装饰器:每个详细装饰器可以单独提供特定的功能,也可以组合多个装饰器来构建复杂的举动。
        基于上面的核心组件,装饰器模式的实现主要有以下五个步调。
        1、界说组件接口。首先,界说一个接口或抽象类,它声明白所有详细组件和装饰器都应该实现的方法。这一步,确保了所有装饰器和组件之间的兼容性。
        2、创建详细组件。接下来,实现组件接口的详细类,这些类代表了不需要任何额外功能的底子对象。
        3、创建抽象装饰器。界说一个装饰器类,它同样实现了组件接口,并持有一个指向其他组件的引用。通常,这个类会有一个构造函数担当一个组件作为参数,并将其保存起来。这样做的目标是:为了能够将任何实现了组件接口的对象传递给装饰器,包括其他装饰器。
        4、创建详细装饰器。为每一个想要添加的新功能创建详细的装饰器类。每个详细装饰器都将覆盖从抽象装饰器继续下来的方法,在其中加入自己的逻辑,同时调用被包装组件相应的方法。这样就实现了在原有举动的底子上,添加新功能的目标。
        5、使用装饰器。在应用层代码中,可以通过将详细组件传递给装饰器并逐步叠加更多的装饰器来构建所需的功能组合。最终得到的对象包含了所有期望的举动,而底子组件自己并没有被修改。

实战剖析

        在下面的实战代码中,我们使用装饰器模式创建了富含各种调料(牛奶、糖、奶油)的咖啡。
        首先,我们界说了一个组件接口CCoffee,它声明白所有详细咖啡和装饰器都必须实现的方法GetDesc和CalcCost。其中,GetDesc函数返回咖啡的描述信息,CalcCost函数返回咖啡的价格。
        接着,我们界说了详细组件CAmericanoCoffee。它实现了CCoffee接口,代表一杯底子的美式咖啡。装饰器基类CCoffeeDecorator也继续自CCoffee,并持有一个指向其他咖啡对象的指针m_pCoffee。默认情况下,GetDesc函数和CalcCost函数会调用被包装对象相应的方法,即它自己不改变任何举动。
        详细装饰器CMilkDecorator、CSugarDecorator、CCreamDecorator分别表示加牛奶、加糖、加奶油的装饰器。每个装饰器继续自CCoffeeDecorator,并重写了GetDesc和CalcCost函数,在原有底子上增加了特定的调料描述和价格。
        末了,在main函数中,我们创建了一杯美式咖啡,并将其赋值给pCoffee。然后,我们逐步添加调料,分别创建了CMilkDecorator、CSugarDecorator、CCreamDecorator对象,每次都将前一个对象作为新装饰器的构造参数传递进去。这样就形成了一个装饰器链,每个新的装饰器都会在其内部保存对前一个对象的引用,并在其上添加新的功能。
  1. #include <iostream>
  2. #include <string>
  3. using namespace std;
  4. // 组件:咖啡
  5. class CCoffee
  6. {
  7. public:
  8.     virtual ~CCoffee() {}
  9.     virtual string GetDesc() const = 0;
  10.     virtual double CalcCost() const = 0;
  11. };
  12. // 具体组件:美式咖啡
  13. class CAmericanoCoffee : public CCoffee
  14. {
  15. public:
  16.     string GetDesc() const
  17.     {
  18.         return "Americano Coffee";
  19.     }
  20.     double CalcCost() const
  21.     {
  22.         return 30.0;
  23.     }
  24. };
  25. // 装饰器基类
  26. class CCoffeeDecorator : public CCoffee
  27. {
  28. public:
  29.     CCoffeeDecorator(CCoffee* pCoffee) : m_pCoffee(pCoffee) {}
  30.     string GetDesc() const
  31.     {
  32.         return m_pCoffee->GetDesc();
  33.     }
  34.     double CalcCost() const
  35.     {
  36.         return m_pCoffee->CalcCost();
  37.     }
  38. protected:
  39.     CCoffee* m_pCoffee;
  40. };
  41. // 牛奶装饰器
  42. class CMilkDecorator : public CCoffeeDecorator
  43. {
  44. public:
  45.     CMilkDecorator(CCoffee* c) : CCoffeeDecorator(c) {}
  46.     string GetDesc() const
  47.     {
  48.         return CCoffeeDecorator::GetDesc() + ", Milk";
  49.     }
  50.     double CalcCost() const
  51.     {
  52.         return CCoffeeDecorator::CalcCost() + 1.5;
  53.     }
  54. };
  55. // 糖装饰器
  56. class CSugarDecorator : public CCoffeeDecorator
  57. {
  58. public:
  59.     CSugarDecorator(CCoffee* c) : CCoffeeDecorator(c) {}
  60.     string GetDesc() const
  61.     {
  62.         return CCoffeeDecorator::GetDesc() + ", Sugar";
  63.     }
  64.     double CalcCost() const
  65.     {
  66.         return CCoffeeDecorator::CalcCost() + 1.0;
  67.     }
  68. };
  69. // 奶油装饰器
  70. class CCreamDecorator : public CCoffeeDecorator
  71. {
  72. public:
  73.     CCreamDecorator(CCoffee* c) : CCoffeeDecorator(c) {}
  74.     string GetDesc() const
  75.     {
  76.         return CCoffeeDecorator::GetDesc() + ", Cream";
  77.     }
  78.     double CalcCost() const
  79.     {
  80.         return CCoffeeDecorator::CalcCost() + 2.0;
  81.     }
  82. };
  83. int main()
  84. {
  85.     // 创建美式咖啡
  86.     CCoffee* pCoffee = new CAmericanoCoffee();
  87.     // 加牛奶
  88.     CCoffee* pMilkCoffee = new CMilkDecorator(pCoffee);
  89.     // 加糖
  90.     CCoffee* pSugarCoffee = new CSugarDecorator(pMilkCoffee);
  91.     // 加奶油
  92.     CCoffee* pCreamCoffee = new CCreamDecorator(pSugarCoffee);
  93.     // 输出:Americano Coffee, Milk, Sugar, Cream
  94.     cout << pCreamCoffee->GetDesc() << endl;
  95.     // 输出:34.5
  96.     cout << pCreamCoffee->CalcCost() << endl;
  97.     // 释放内存
  98.     delete pCreamCoffee;
  99.     delete pSugarCoffee;
  100.     delete pMilkCoffee;
  101.     delete pCoffee;
  102.     return 0;
  103. }
复制代码

总结

        可以看到,通过组合不同的装饰器,可以创建出多种多样的功能组合,而无需为每一种大概的功能组合创建新的子类。这样减少了代码量,进步了代码的可维护性和复用性。但随着装饰器数量的增加,系统的整体复杂度也会相应提升。过多的装饰器大概会使代码难以理解和维护,特殊是当装饰器之间存在复杂的交互时。别的,每次调用被装饰的方法时,都会经过一系列的装饰器链,这大概导致额外的性能开销。固然通常情况下这种开销是可以担当的,但在性能要求极高的场景下大概需要特殊注意。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

鼠扑

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表