一、基本知识
在我第一次接触装饰器模式的时候,将它和责任链模式分不开,但是事实上它们是有区别的。
1.定义
动态地给一个对象增加一些额外的职责。就增加功能而言,装饰器模式比生产子类更为灵活。
2.背景示例
普通员工有销售奖金,累计奖金,部门经理除此之外还有团队奖金;后面可能会添加环比增长奖金,同时可能针对不同的职位产生不同的奖金组合。
3.要点
①通过采用组合而非继承的手法, 装饰器模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。 避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
②不是解决“多子类衍生问题”问题,而是解决“父类在多个方向上的扩展功能”问题。
③装饰器模式把一系列复杂的功能分散到每个装饰器当中,一般一个装饰器只实现一个功能,实现复用装饰器的功能。
二、结构图
对于本人来说,先看结构图的话,看不懂,都是先看示例代码,回头来再看结构图才看得懂。
三、实例说明
1.普通代码
#include<iostream>
#include<string>
using namespace std;
// 普通员工有销售奖金,累计奖金,部门经理除此之外还有团队奖金;后面可能会添加环比增长奖金,同时可能产生不同的奖金组合;
// 销售奖金 = 当月销售额 * 4%
// 累计奖金 = 总的回款额 * 2%
// 部门奖金 = 团队销售额 * 1%
// 环比奖金 = (当月销售额-上月销售额) * 1%
// 销售后面的参数可能会调整
class Context {
public:
bool isMgr;
string name;
double groupsale;
};
class Bonus {
public:
double CalcBonus(Context& ctx) {
double bonus = 0.0;
bonus += CalcMonthBonus(ctx);
bonus += CalcSumBonus(ctx);
if (ctx.isMgr) {
bonus += CalcGroupBonus(ctx);
}
return bonus;
}
private:
double CalcMonthBonus(Context& ctx) {
double bonus;
/*一系列操作求销售奖金*/
return bonus;
}
double CalcSumBonus(Context& ctx) {
double bonus;
/*一系列操作求总的回款额*/
return bonus;
}
double CalcGroupBonus(Context& ctx) {
double bonus;
/*一系列操作求部门奖金*/
return bonus;
}
};
int main() {
Context ctx;
// 设置 ctx
Bonus* bonus = new Bonus;
bonus->CalcBonus(ctx);
}
2.装饰器模式代码
#include <iostream>
#include <string>
// 普通员工有销售奖金,累计奖金,部门经理除此之外还有团队奖金;后面可能会添加环比增长奖金,同时可能产生不同的奖金组合;
// 销售奖金 = 当月销售额 * 4%
// 累计奖金 = 总的回款额 * 0.2%
// 部门奖金 = 团队销售额 * 1%
// 环比奖金 = (当月销售额-上月销售额) * 1%
// 销售后面的参数可能会调整
using namespace std;
class Context {
public:
bool isMgr;
string name;
double groupsale;
};
// 试着从职责出发,将职责抽象出来
class CalcBonus {
public:
CalcBonus(CalcBonus * c = nullptr) : cc(c) {}
virtual double Calc(Context &ctx) {
return 0.0; // 基本工资
}
virtual ~CalcBonus() {}
protected:
CalcBonus* cc;//通过组合的方式
};
class CalcMonthBonus : public CalcBonus {
public:
CalcMonthBonus(CalcBonus * c) : CalcBonus(c) {}
virtual double Calc(Context &ctx) {
double mbonus;
/*求销售奖金操作*/
return mbonus + cc->Calc(ctx);
}
};
class CalcSumBonus : public CalcBonus {
public:
CalcSumBonus(CalcBonus * c) : CalcBonus(c) {}
virtual double Calc(Context &ctx) {
double sbonus;
/*求累计奖金操作*/
return sbonus + cc->Calc(ctx);
}
};
class CalcGroupBonus : public CalcBonus {
public:
CalcGroupBonus(CalcBonus * c) : CalcBonus(c) {}
virtual double Calc(Context &ctx) {
double gbnonus;
/*求部门奖金操作*/
return gbnonus + cc->Calc(ctx);
}
};
class CalcCycleBonus : public CalcBonus {
public:
CalcCycleBonus(CalcBonus * c) : CalcBonus(c) {}
virtual double Calc(Context &ctx) {
double gbnonus ;
/*求环比奖金操作*/
return gbnonus + cc->Calc(ctx);
}
};
int main() {
// 1. 普通员工
Context ctx1;
ctx1.isMgr = false;
CalcBonus *base = new CalcBonus();
CalcBonus *cb1 = new CalcSumBonus(base);
CalcBonus *cb2 = new CalcMonthBonus(cb1);
cb2->Calc(ctx1);//普通员工奖金只求销售奖金和部门奖金
// 2. 部门经理
Context ctx2;
ctx2.isMgr = true;
CalcBonus *cb3 = new CalcGroupBonus(cb2);
cb3->Calc(ctx2);//部门奖金求销售、累计、部门奖金(环比奖金暂时没有设置)
}
可以看见,装饰器和责任链最大的区别是装饰器模式是最主要的是组合手法。而责任链是继承,然后每个子类的实例之间通过链表的方式低度耦合。装饰器之所以采用组合,是因为子类并不只是其中一个子类符合条件进行操作,而是多个子类都要操作,比如奖金,是通过多个子类求出来的。
而责任链模式通常是某个子类来解决这个事情,而链表是通常按照一定的先后顺序将子类低度耦合起来,有事件要处理时,通过链表遍历这个链表上的类,找到适合处理这个事情的类,这个往往是一个类就可以处理完。
当然,其实大部分可以用装饰器模式的很可能也可以用责任链模式,因为责任链模式只是往往一个事件只需要一个类处理,但是也可以是多个类都要处理,但是通过执行先后顺序将类用链表连接起来,就像是一个工厂的流水线一样。