• 装饰模式-C++实现


    装饰模式是一种结构型设计模式,也是一种单一职责模式,它允许你在不修改原始类的情况下,通过将对象包装在装饰器的对象中,动态地增加功能和行为。

    接下来我会逐步分析上面这段话各个字的意思。

    首先是装饰,什么是装饰?举一个例子,我们现在有一个需求:绘制各种形状,我们可以绘制各种各样的形状,像圆、正方形、椭圆等等。但是只绘制了形状之后我们还要为形状添加颜色、宽度、画笔、画刷等等,那像颜色这些东西就是所谓的装饰

    装饰模式就是允许我们不修改已经写好的类的情况下,动态地增加这些装饰

    比如说现在我们已经为形状添加了颜色这个装饰,然后需求变了,我们要添加宽度这一装饰,那我们通过装饰模式就可以不修改原始类,重新写一个宽度类为形状添加装饰

    说到这儿,对装饰模式的字面意思就解释清楚了,接下来我们看看如何实现装饰模式。

    场景:
    假设我们现在开了一个饮品店,里面卖两种饮料:柠檬水和咖啡。同时我们为每种饮料提供了两种小料:糖和牛奶,可以根据客人的需要进行添加,其中添加一次糖的价格为1元,牛奶的价格为2元。

    我先用常规思路来解决这个需求,然后用装饰模式,这样做比较大家就知道装饰模式具体干了什么。

    常规解法

    // 抽象组件类
    class Drink
    {
    public:
    
    	virtual ~Drink() {}
    
    	virtual void AddDescription(const std::string& _des) = 0;
    
    	virtual void CalCost(const int& _cost) = 0;
    };
    
    // 具体组件类 柠檬水
    class Lemonade
    	: public Drink
    {
    public:
    
    	virtual void AddDescription(const std::string& _des) override
    	{
    		description_ += " " + _des;
    
    		std::cout << "柠檬水总共添加的小料:" << description_ << std::endl;
    	}
    
    	virtual void CalCost(const int& _cost) override
    	{
    		cost_ += _cost;
    
    		std::cout << "总花费:" << cost_ << std::endl;
    	}
    
    private:
    
    	// 添加的小料
    	std::string description_;
    
    	// 花费
    	int cost_;
    };
    
    // 具体组件类 咖啡
    class Coffee
    	: public Drink
    {
    public:
    
    	virtual void AddDescription(const std::string& _des) override
    	{
    		description_ += " " + _des;
    
    		std::cout << "咖啡总共添加的小料:" << description_ << std::endl;
    	}
    
    	virtual void CalCost(const int& _cost) override
    	{
    		cost_ += _cost;
    
    		std::cout << "总花费:" << cost_ << std::endl;
    	}
    
    private:
    
    	// 添加的小料
    	std::string description_;
    
    	// 花费
    	int cost_;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    int main()
    {
        // 策略模式用法
    	// TestStrategy();
    
    	// TestObserver();
    
    	std::shared_ptr<Drink> drink = std::make_shared<Lemonade>();
    	drink->AddDescription("牛奶");
    	drink->CalCost(2);
    
    	drink->AddDescription("糖");
    	drink->CalCost(1);
    
    	//TestDecorator();
    
        system("pause");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    输出:

    柠檬水总共添加的小料: 牛奶
    总花费:2
    柠檬水总共添加的小料: 牛奶 糖
    总花费:3
    
    • 1
    • 2
    • 3
    • 4

    这样写,我们每次添加小料时需要使用AddDescription方法和CalCost方法。大家可能会说这不是很简单吗,根本没有那么复杂,代码的可读性也很好。

    这是因为我们的这个例子很简单,只是传了一个“牛奶”和“糖”的参数和它的价钱。试想一下在更复杂的需求中我们需要将“牛奶”换成更复杂、规模更庞大的算法怎么办,我们只能去修改AddDescription方法,如果添加的装饰越来越多,AddDescription这个方法的可读行、可扩展性可就没那么好了。

    并且这样的设计违背了开放封闭原则,即对扩展开放,对修改关闭。

    现在我们来看看装饰模式怎么完成。

    装饰模式由四个部分组成,也就是四个组件:抽象组件、具体组件、抽象装饰、具体装饰

    现在我们来分析一下需求,饮品店的饮品就是抽象组件,柠檬水和咖啡是具体组件,因为它们都是饮料这一范畴,牛奶和糖就是具体装饰。

    关于这个抽象装饰可有可无,我在这边简要说明一下为什么需要它。

    根据马丁福勒的重构理论:如果某一个类,它有多个子类都有同样的字段时应该把它往上提

    看完下面的代码大家就会明白这句话的含义。

    // 抽象组件类
    class Drink
    {
    public:
    
    	virtual ~Drink() {}
    
    	virtual void AddDescription(const std::string& _des) = 0;
    
    	virtual void CalCost(const int& _cost) = 0;
    };
    
    // 具体组件类 柠檬水
    class Lemonade
    	: public Drink
    {
    public:
    
    	virtual void AddDescription(const std::string& _des) override
    	{
    		description_ += " " + _des;
    
    		std::cout << "柠檬水总共添加的小料:" << description_ << std::endl;
    	}
    
    	virtual void CalCost(const int& _cost) override
    	{
    		cost_ += _cost;
    
    		std::cout << "总花费:" << cost_ << std::endl;
    	}
    
    private:
    
    	// 添加的小料
    	std::string description_;
    
    	// 花费
    	int cost_;
    };
    
    // 具体组件类 咖啡
    class Coffee
    	: public Drink
    {
    public:
    
    	virtual void AddDescription(const std::string& _des) override
    	{
    		description_ += " " + _des;
    
    		std::cout << "咖啡总共添加的小料:" << description_ << std::endl;
    	}
    
    	virtual void CalCost(const int& _cost) override
    	{
    		cost_ += _cost;
    
    		std::cout << "总花费:" << cost_ << std::endl;
    	}
    
    private:
    
    	// 添加的小料
    	std::string description_;
    
    	// 花费
    	int cost_;
    };
    
    // 抽象装饰类
    class Description
    	: public Drink
    {
    protected:
    	std::shared_ptr<Drink> drink_;
    
    	Description(std::shared_ptr<Drink> _drink)
    	{
    		this->drink_ = _drink;
    	}
    };
    
    // 具体装饰类 牛奶
    class Milk
    	: public Description
    {
    public:
    
    	Milk(std::shared_ptr<Drink> _drink)
    		: Description(_drink)
    	{
    
    	}
    
    	virtual void AddDescription(const std::string& _des)
    	{
    		drink_->AddDescription(_des);
    	}
    
    	virtual void CalCost(const int& _cost)
    	{
    		drink_->CalCost(_cost);
    	}
    };
    
    // 具体装饰类 糖
    class Sugar
    	: public Description
    {
    public:
    
    	Sugar(std::shared_ptr<Drink> _drink)
    		: Description(_drink)
    	{
    
    	}
    
    	virtual void AddDescription(const std::string& _des)
    	{
    		drink_->AddDescription(_des);
    	}
    
    	virtual void CalCost(const int& _cost)
    	{
    		drink_->CalCost(_cost);
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    void TestDecorator()
    {
    	// 用户下单了一杯柠檬水
    	std::shared_ptr<Drink> drink = std::make_shared<Lemonade>();
    
    	// 用户想要为柠檬水添加牛奶
    	std::shared_ptr<Milk> milk = std::make_shared<Milk>(drink);
    	// 添加牛奶
    	milk->AddDescription("牛奶");
    	// 一杯牛奶价钱2元
    	milk->CalCost(2);
    
    	// 用户想要为柠檬水加糖
    	std::shared_ptr<Sugar> sugar = std::make_shared<Sugar>(drink);
    	// 加糖
    	sugar->AddDescription("糖");
    	// 糖的价钱1元
    	sugar->CalCost(1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    输出:

    柠檬水总共添加的小料: 牛奶
    总花费:2
    柠檬水总共添加的小料: 牛奶 糖
    总花费:3
    
    • 1
    • 2
    • 3
    • 4

    以上就是一个标准的装饰模式写法,大家可以比对常规写法看看二者有什么不同。

    在装饰模式下我们可以动态的扩展功能,即添加装饰,比如我们需要添加一个新的小料:牛油果。我们就可以写一个牛油果的具体装饰类,在运行时动态的添加牛油果这一小料,并且这样的写法没有违背开放封闭原则。

    现在我们回过头来说一下这个抽象装饰类,它里面只有一个基类的指针,这个指针是我们在外部传入的,如果不加这个抽象装饰类,这个指针就需要在我们的具体装饰类里添加。

    // 具体装饰类 牛奶
    class Milk
    	: public Drink
    {
    public:
    
    	Milk(std::shared_ptr<Drink> _drink)
    		: Drink()
    	{
    		drink_ = _drink;
    	}
    
    	virtual void AddDescription(const std::string& _des)
    	{
    		drink_->AddDescription(_des);
    	}
    
    	virtual void CalCost(const int& _cost)
    	{
    		drink_->CalCost(_cost);
    	}
    
    private:
    	std::shared_ptr<Drink> drink_;
    };
    
    // 具体装饰类 糖
    class Sugar
    	: public Drink
    {
    public:
    
    	Sugar(std::shared_ptr<Drink> _drink)
    		: Drink()
    	{
    		drink_ = _drink;
    	}
    
    	virtual void AddDescription(const std::string& _des)
    	{
    		drink_->AddDescription(_des);
    	}
    
    	virtual void CalCost(const int& _cost)
    	{
    		drink_->CalCost(_cost);
    	}
    
    private:
    	std::shared_ptr<Drink> drink_;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    我们的具体装饰类就应该这么写,它不去继承抽象装饰类,而是去继承我们的抽象组件类,这样的话我们每个装饰类都要添加,很明显非常傻,所以我们选择把它“提上去”。

    如果某一个类,它有多个子类都有同样的字段时应该把它往上提

    相同的字段就是:std::shared_ptr drink_;

    这种提法有两种,第一种是直接提到Drink类,但是我们的柠檬水和咖啡类又不需要这个字段,所以又是一种浪费。

    第二种就是写一个抽象装饰类,提到这个里面供我们的具体装饰类使用。

    装饰模式的好处有:

    • 动态地扩展功能:装饰模式允许你在运行时动态地添加、移除或修改对象的行为。你可以使用不同的装饰类来组合对象的不同行为,以实现各种功能组合效果。
    • 遵循开闭原则:装饰模式遵循开闭原则,即对扩展开放,对修改关闭。你可以通过添加新的装饰类来扩展对象的功能,而无需修改原有的代码,从而减少了对现有代码的侵入性。
    • 单一职责原则:装饰模式将功能划分到不同的装饰类中,使得每个装饰类只负责一个特定的功能,从而遵循了单一职责原则。这样可以提高代码的可读性、可维护性和可扩展性。
    • 灵活组合对象:装饰模式允许你按照自己的需求,自由地组合对象的功能。你可以将多个装饰类按照一定的顺序组合起来,以实现不同的功能效果。

    总结:装饰模式提供了一种灵活且可扩展的方式来添加功能,同时保持了代码的可读性和可维护性。它在设计复杂的对象结构时特别有用,可以避免使用大量的子类来实现各种功能的组合。

  • 相关阅读:
    面试问题总结(2)
    使用canvas(2d)+js实现一个简单的傅里叶级数绘制方波图
    docker方式启动一个java项目-Nginx本地有代码,并配置反向代理
    算法设计与分析 实验4 动态规划法求扔鸡蛋问题
    STM32CubeMX驱动INA226芯片
    sface人脸相似度检测
    FHQ-Treap 简介
    Kotlin协程中的作用域 `GlobalScope`、`lifecycleScope` 和 `viewModelScope`
    冒泡排序概览(java+R_优化以及双向冒泡代码)
    SpringBoot任务详解
  • 原文地址:https://blog.csdn.net/m0_51415606/article/details/134431885