装饰模式是一种结构型设计模式,也是一种单一职责模式,它允许你在不修改原始类的情况下,通过将对象包装在装饰器的对象中,动态地增加功能和行为。
接下来我会逐步分析上面这段话各个字的意思。
首先是装饰,什么是装饰?举一个例子,我们现在有一个需求:绘制各种形状,我们可以绘制各种各样的形状,像圆、正方形、椭圆等等。但是只绘制了形状之后我们还要为形状添加颜色、宽度、画笔、画刷等等,那像颜色这些东西就是所谓的装饰。
装饰模式就是允许我们不修改已经写好的类的情况下,动态地增加这些装饰。
比如说现在我们已经为形状添加了颜色这个装饰,然后需求变了,我们要添加宽度这一装饰,那我们通过装饰模式就可以不修改原始类,重新写一个宽度类为形状添加装饰。
说到这儿,对装饰模式的字面意思就解释清楚了,接下来我们看看如何实现装饰模式。
场景:
假设我们现在开了一个饮品店,里面卖两种饮料:柠檬水和咖啡。同时我们为每种饮料提供了两种小料:糖和牛奶,可以根据客人的需要进行添加,其中添加一次糖的价格为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类,但是我们的柠檬水和咖啡类又不需要这个字段,所以又是一种浪费。
第二种就是写一个抽象装饰类,提到这个里面供我们的具体装饰类使用。
装饰模式的好处有:
总结:装饰模式提供了一种灵活且可扩展的方式来添加功能,同时保持了代码的可读性和可维护性。它在设计复杂的对象结构时特别有用,可以避免使用大量的子类来实现各种功能的组合。