装饰模式-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_;
};
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;
}

输出:

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

这样写,我们每次添加小料时需要使用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);
	}
};
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);
}

输出:

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

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

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

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

// 具体装饰类 牛奶
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_;
};

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

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

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

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

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

装饰模式的好处有:

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

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

你可能感兴趣的:(设计模式,c++,设计模式,装饰器模式)