(三)详解装饰者模式

一.使用场景

主类不需要更改,但是子类需要经常更改,扩展。同时需要能实现动态扩展。可以动态的增加或者删除新的职责。策略模式和装饰者模式很像但是可扩展的功能程度不同。策略模式往往添加了一个和之前几乎完全不一样的功能,而装饰者模式是对之前有的功能的一种修饰。

二.核心

  • 装饰者模式的定义。装饰者模式动态的将额外责任附加到对象上,对于扩展功能,装饰者提供子类化之外的弹性替代方案。
  • 我们拥有一个基本的组件。每个组件可以动态的加上新行为,并可以独立使用。我们想在不同情况下对这个组件随意组合上不同的装饰。每个装饰者拥有一个组件的实例变量,这个实例变量是为了后序一层一层调用使用的。我们需要用所需的不同装饰者,反复包裹自身的对象也就是组件。同时通过不同层装饰之间的多态可以实现调用不同的装饰层。
  • 装饰者和所装饰对象有着相同的类型,可以用一个或多个装饰者包裹一个对象。装饰者在委托给所装饰对象之前或者之后添加自己的行为用来做剩下的工作(很像递归一层一层往回调用)。对象可以在任意时候被装饰
  • 装饰者模式遵循开放关闭原则。装饰者模式允许行为扩展,不允许修改已有的代码。装饰者模式涉及到一群装饰者类,用来包装具体的组件。装饰者反映的就是其本身内涵的组件的类型。装饰者会导致设计中出现许多小对象,过度使用会导致代码变得复杂。

三.例子

假设我们要模拟星巴克的业务逻辑。我们拥有不同的饮料作为组件也就是基础原件,同时我们拥有不同的调料作为装饰者。不同的配方有不同的价钱,我们希望能通过不同配料的组合计算出每一杯总共的费用。

我们首先需要让所有可能的饮料当成组件,我们可以提供一个组件接口,这样可以通过继承实现不同的饮料接口。

class Beverage {    
//  纯虚接口 为基本组件提供接口用来实现不同类型的组件
public:
    virtual ~Beverage() = default;
    virtual std::string getDescription() const = 0;        //打印当前层组件信息
    virtual double cost() const = 0;    //组件自身也有费用
};

根据这个接口我们可以实现出不同类型的组件。这里以深度烘焙饮料作为组件。我们可以根据接口实现出多种不同的具体组件。

class DarkRoast: public Beverage {  //组件具体实现
public:

    std::string getDescription() const    //当前饮料的描述
    {
         return "Dark Roast Coffee";
    }

    double cost() const    //当前饮料的费用
    {
         return 0.99;
    }
};

有了组件接下来可以实现装饰者。每个装饰者包裹一个组件的实例变量。装饰者本身是一个接口。我们可以通过装饰者实现出不同具体的装饰对象。同时装饰者继承自组件类型,同时装饰者和组件有着同样的类型,这意味着装饰者可以承接任何样子的组件。不论这个组件上有没有装饰。相当于装饰者包裹组件后并没有更改其类型,还可以用其他装饰者装饰。(自己包裹自己)。

class CondimentDecorator: public Beverage {
protected:
    CondimentDecorator(std::unique_ptr beverage) 
    : beverage(beverage) 
    {};        //包裹组件前记录下包装前的组件对象

    std::unique_ptr beverage;    //用来存放当前组件的实例,实现多态时会用到它
};

通过这个装饰者接口我们可以实现多个不同种类的装饰者。这里我们让不同的调料充当装饰者

class Milk: public CondimentDecorator {
public:
    Milk(std::unique_ptr beverage) 
    : CondimentDecorator(beverage)    //先调用装饰者保存下实例
    {
        
    }

    std::string getDescription()  const     //先调用实例内的描述在结合自身当前的描述(递归调用)
    {
        return beverage->getDescription() + ", Steamed Milk";
    }

    double cost() const 
    {
        return beverage->cost() + 0.10;    //先调用实例内的费用在结合自身
    }

};

他的调用逻辑如下图所示(三)详解装饰者模式_第1张图片在创建完对象后,去保存的组件里调用上一层的函数,保存在接口里,这里是摩卡。上一层继续向上调用,一层一层剥洋葱递归,直到最后拿到最内层组件的值。无论有多少层装饰者都能一视同仁的调用。

四.拓展

我们可以根据要求对装饰者进行拓展。假如我们想根据大小杯不同的料收取不同的费用。我们只需要在装饰者接口内添加一个获取最基本组件的杯子大小的函数。然后向调用cost一样,每一层装饰者对拿到的杯子大小进行判断,再进行价钱的调整

五.缺点

使用装饰者模式会产生大量的小类,给维护产生负担。同时引入装饰者会增加实例化组件需要的代码复杂度。(可以通过工厂或生成器解决)

六.和策略模式对比

1.用途

  • 策略模式:策略模式用于定义一组算法,将每个算法封装成独立的策略对象,并使这些策略对象可以互相替换,以便在运行时动态地选择不同的算法来执行任务。策略模式主要关注的是如何在不同的算法之间进行切换和选择。
  • 装饰者模式:装饰者模式用于动态地为对象添加额外的功能或行为,而不需要修改其原始类。它允许你将对象包装在一个或多个装饰者类中,每个装饰者类都可以添加额外的行为,而不改变原始对象的结构。装饰者模式主要关注的是在不改变类结构的情况下扩展对象的功能。

2.关注点

  • 策略模式:提供一种方法来动态选择这些策略以满足不同的需求。它强调了算法的选择和切换。
  • 装饰者模式:关注于动态地为对象添加新的功能,通过将对象包装在装饰者中,可以逐渐构建复杂的装饰链。它强调了功能的组合和扩展

3.变化性

  • 策略模式在不同的策略之间进行切换,通常是在运行时动态选择策略,因此主要处理不同算法的变化。
  • 装饰者模式主要处理对象功能的扩展,可以在运行时动态添加新的功能,而不影响对象的类。

你可能感兴趣的:(设计模式,c++,观察者模式)