意图
装饰模式是一种结构型设计模式,允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。
问题
假设你正在开发一个提供通知功能的库, 其他程序可使用它向用户发送关于重要事件的通知。
库的最初版本基于通知器 Notifier
类,其中只有很少的几个成员变量,一个构造函数和一个send
发送方法。该方法可以接收来自客户端的消息参数,并将该消息发送给一系列的邮箱,邮箱列表则是通过构造函数传递给通知器的。作为客户端的第三方程序仅会创建和配置通知器对象一次,然后在有重要事件发生时对其进行调用。
用户希望使用除邮件通知之外的功能。许多用户会希望接收关于紧急事件的手机短信,还有些用户希望在微信上接收消息,而公司用户则希望在 QQ 上接收消息。
首先扩展通知器类
,然后在新的子类中加入额外的通知方法。现在客户端要对所需通知形式的对应类进行初始化,然后使用该类发送后续所有的通知消息。
创建一个特殊子类来将多种通知方法组合在一起以解决该问题。 但这种方式会使得代码量迅速膨胀,不仅仅是程序库代码,客户端代码也会如此。
子类组合爆炸
解决方案
当你需要更改一个对象的行为时,第一个跳入脑海的想法就是扩展它所属的类。但是,你不能忽视继承可能引发的几个严重问题。
- 继承是静态的。你无法在运行时更改已有对象的行为,只能使用由不同子类创建的对象来替代当前的整个对象。
- 子类只能有一个父类。大部分编程语言不允许一个类同时继承多个类的行为。
其中一种方法是用组合,而不是继承。两者的工作方式几乎一模一样:一个对象包含指向另一个对象的引用,并将部分工作委派给引用对象;继承中的对象则继承了父类的行为,它们自己能够完成这些工作。
将各种通知方法放入装饰
客户端代码必须将基础通知器放入一系列自己所需的装饰中。因此最后的对象将形成一个栈结构。
示例代码
#include <iostream>
class Notifier {
public:
virtual ~Notifier() {}
virtual std::string Send() const = 0;
};
class ConcreteNotifier : public Notifier {
public:
std::string Send() const {
return "send";
}
};
class BaseDecorator : public Notifier {
protected:
Notifier* m_notifier;
public:
BaseDecorator(Notifier* notifier) : m_notifier(notifier) {
}
std::string Send() const {
return this->m_notifier->Send();
}
};
class QQDecorator : public BaseDecorator {
public:
QQDecorator(Notifier* notifier) : BaseDecorator(notifier) {
}
std::string Send() const {
return "QQ(" + BaseDecorator::Send() + ")";
}
};
class WeChatDecorator : public BaseDecorator {
public:
WeChatDecorator(Notifier* notifier) : BaseDecorator(notifier) {
}
std::string Send() const {
return "WeChat(" + BaseDecorator::Send() + ")";
}
};
void ClientCode(Notifier* notifier) {
// ...
std::cout << "RESULT: " << notifier->Send();
// ...
}
int main() {
Notifier* stack = new ConcreteNotifier;
ClientCode(stack);
std::cout << "\n";
BaseDecorator* QQ = new QQDecorator(stack);
BaseDecorator* WeChat= new WeChatDecorator(QQ);
ClientCode(WeChat);
std::cout << "\n";
delete stack;
delete QQ;
delete WeChat;
return 0;
}
运行结果:
RESULT: send
RESULT: WeChat(QQ(send))
装饰模式结构
装饰模式UML图
小结
适合应用场景:
- 如果你希望在无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的行为。
- 希望在几个独立维度上扩展一个类。
- 需要在运行时切换不同实现方法。
优点:
- 无需创建新子类即可扩展对象的行为。
- 可以在运行时添加或删除对象的功能。
- 可以用多个装饰封装对象来组合几种行为。
- 单一职责原则。可以将实现了许多不同行为的一个大类拆分为多个较小的类。
缺点:
- 在封装器栈中删除特定封装器比较困难。
- 实现行为不受装饰栈顺序影响的装饰比较困难。
- 各层的初始化配置代码看上去可能会很糟糕。
参考
22种设计模式:refactoringguru.cn/design-patterns
《设计模式:可复用面向对象软件的基础》