一、装饰模式概述:装饰模式是动态地将责任添加到具体对象上而不影响这个对象所对应的类或者基类的方法。当需要拓展功能的时候,提供了一种比传统继承方式更为灵活的选择。
二、应用场景:
(a)需要动态地给一个对象添加功能,且这些功能也可以动态地被撤销时。
(b)当使用继承不利于系统的维护和扩展时或者直接不允许使用继承的方式对系统进行扩展时。
(c)一个原始类同时生成了多个对象,在不影响其他对象的前提下,以动态、透明的方式为单个对象添加新的职责,而不修改原始类。试想一下,如果频繁修改原始类的话,会变得非常庞大和复杂。
三、装饰模式场景模拟:食堂买饭
假设食堂提供三种主食,分别是米饭(2.5元)、面条(6.6元);然后食堂也提供三种菜品,分别是鸡蛋(0.5元)、牛肉(10元)、火腿(5元)。在必选主食的前提下可以在三种菜品中随意搭配,据此计算最终价格。
上述案列其实就是典型的装饰模式,可以看成是用菜品去装饰主食,而且可以按照任何顺序进行装饰,你可以先加鸡蛋再加牛肉,也可以先加牛肉再加鸡蛋,最后计算总价。
装饰模式-UML类图
废话少说,直接挒上C++代码
步骤1:定义一个抽象基类Food,并且声明一个公共接口myInterface(),在子类中通过这个接口来给具体对象添加职责。
class Food
{
public:
Food(){}
virtual ~Food(){}
virtual void myInterface() = 0; // 为后续的各种装饰操作提供一个统一的接口
void setPrice(double s) { mPrice = s; }
double getPrice() { return mPrice; }
private:
double mPrice; // 食物价格
};
步骤2:从Food类中派生出2个具体的主食类(米饭、馒头)
// 米饭类
class FoodRice :public Food
{
public:
FoodRice() {
setPrice(2.5);
}
~FoodRice() {}
virtual void myInterface()
{
std::cout << "米饭";
}
};
// 面条类
class FoodNoodle :public Food
{
public:
FoodNoodle()
{
setPrice(6.6);
}
~FoodNoodle() {}
virtual void myInterface()
{
std::cout << "面条";
}
};
步骤3:定义抽象装饰类Decorator,继承自Food类,并在其内部维护一个Food类型的指针,将各种装饰对象与被装饰对象组织关联起来。
class Decorator :public Food
{
public:
Decorator(Food* food)
{
mFood = food;
}
virtual ~Decorator(){}
virtual void myInterface()
{
this->mFood->myInterface();
}
Food* getFood() // 父类的私有成员mFood 在子类中调用get()方法获取
{
return this->mFood;
}
void setFood(Food* food) // 父类的私有成员mFood 在子类中调用set()方法设置
{
this->mFood = food;
}
private:
Food* mFood = nullptr; // 关键:装饰类中维护一个被装饰对象的基类指针
};
步骤4:定义具体的装饰对象(加鸡蛋、加牛肉、加香肠)
// 装饰类(加鸡蛋)
class EggDecorator :public Decorator
{
public:
EggDecorator(Food* food): Decorator(food) // 通过装饰类的构造函数传入被装饰对象
{
this->setFood(food);
}
virtual ~EggDecorator(){}
virtual void myInterface()
{
this->getFood()->myInterface(); // getFood()返回的是从构造函数中传入的被装饰对象,故此处调用的是被装饰对象的myInterface()函数
doDecorator(); // 进行装饰操作
}
private:
// 装饰操作:加鸡蛋并重新计算价格
void doDecorator()
{
std::cout << "+鸡蛋";
setPrice(this->getFood()->getPrice() + 0.5); // this->getFood()->getPrice() 返回的是加鸡蛋之前的价格
} // this->setPrice(double) 设置的是加入鸡蛋后的价格
};
// 装饰类(加牛肉)
class BeefDecorator :public Decorator
{
public:
BeefDecorator(Food* food) : Decorator(food)
{
this->setFood(food);
}
virtual ~BeefDecorator() {}
virtual void myInterface()
{
this->getFood()->myInterface();
doDecorator();
}
private:
void doDecorator()
{
std::cout << "+牛肉";
setPrice(this->getFood()->getPrice() + 10);
}
};
// 装饰类(加火腿)
class HamDecorator :public Decorator
{
public:
HamDecorator(Food* food) : Decorator(food)
{
this->setFood(food);
}
virtual ~HamDecorator() {}
virtual void myInterface()
{
this->getFood()->myInterface();
doDecorator();
}
private:
void doDecorator()
{
std::cout << "+火腿";
setPrice(this->getFood()->getPrice() + 5);
}
};
客户端调用代码:
#include <QtCore/QCoreApplication>
#include "Decorator.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 搭配方案1 (面条6.6 + 鸡蛋0.5 = 7.1元)
std::cout << "搭配方案1:";
Food* aaa1 = new FoodNoodle();
Food* bbb1 = new EggDecorator(aaa1);
bbb1->myInterface();
std::cout << "=" << bbb1->getPrice() << '\n' << '\n';
// 搭配方案2 (米饭2.5 + 鸡蛋0.5 + 牛肉10 + 火腿5 = 18元)
std::cout << "搭配方案2:";
Food* aaa2 = new FoodRice(); // aaa2
Food* bbb2 = new EggDecorator(aaa2); // 装饰1(aaa2 + bbb2)
Food* ccc2 = new BeefDecorator(bbb2); // 装饰2(aaa2 + bbb2 + ccc2)
Food* ddd2 = new HamDecorator(ccc2); // 装饰3(aaa2 + bbb2 + ccc2 + ddd2)
// 此处的ddd2->myInterface()其实是一个链式调用。
// 链式调用过程:HamDecorator::myInterface() ——> BeefDecorator::myInterface() ——> EggDecorator::myInterface() ——> FoodRice::myInterface()
// 在每个装饰类的myInterface函数中又加入了具体的装饰操作,所以,装饰顺序与链式调用顺序正好相反。
ddd2->myInterface();
std::cout <<"=" << ddd2->getPrice() << '\n' << '\n';
//搭配方案3 (面条6.6 + 牛肉10 + 火腿5 = 21.6元)
std::cout << "搭配方案2:";
Food* aaa3 = new FoodNoodle();
Food* bbb3 = new BeefDecorator(aaa3);
Food* ccc3 = new HamDecorator(bbb3);
ccc3->myInterface();
std::cout << "=" << ccc3->getPrice() << '\n' << '\n';
return a.exec();
}
客户端代码运行结果如下:
以搭配方案2为例,可以根据下图来理解整个装饰的过程:
装饰模式的优点:
(1)可以对已存在的对象进行修改和包装,在被装饰对象的前面或者后面添加新的行为,而无需修改原始类,符合“开闭原则”。请注意,原始类是不变的,但原始类所生成的对象有了新的行为!
(2)在不确定后续功能的时候,可以先定义一个简单的类,使用设计模式来封装,后续利用装饰类来逐步添加新的功能,最终组合出复杂功能。
(3)装饰类的添加和删除非常方便,在客户端代码中就能轻松实现,因此非常适合用来实现可拔插操作频繁的插件。