C++软件设计模式之装饰器模式
装饰器模式(Decorator Pattern)是C++软件设计模式中的一种结构型设计模式,主要用于解决在不改变现有对象结构的情况下动态地给对象添加新功能的问题。通过使用装饰器模式,可以在运行时为对象添加新的行为,而不需要修改其原始类或创建大量的子类。
装饰器模式的适用场合
-
动态扩展功能:当你需要在不修改现有代码的情况下,动态地为对象添加新的功能时,装饰器模式非常有用。它允许你在运行时通过组合来扩展对象的行为。
-
避免类爆炸:如果使用继承来扩展功能,可能会导致类的数量急剧增加(类爆炸问题)。装饰器模式通过组合的方式避免了这一问题,减少了类的数量。
-
遵循开闭原则:装饰器模式允许在不修改现有代码的情况下扩展功能,符合“开闭原则”(对扩展开放,对修改封闭)。
-
处理具有多个独立功能的对象:当你需要为对象添加多个独立的功能,而这些功能可以任意组合时,装饰器模式非常适用。例如,窗口系统中的边框、滚动条、阴影等功能可以通过装饰器模式动态组合。
-
框架和库的设计:在设计框架或库时,装饰器模式可以用于提供灵活的扩展机制,使用户能够根据自己的需求为对象添加功能。
装饰器模式与树结构的内在关联
装饰器模式确实与树结构存在一定的内在关联。在装饰器模式中,装饰器可以嵌套使用,形成一个链式的结构。每个装饰器可以包装另一个装饰器,最终形成一个类似树结构的层次关系。
-
链式结构:装饰器模式的核心在于通过组合的方式将多个装饰器串联起来,形成一个链式结构。每个装饰器都持有一个对被装饰对象的引用,并且可以在调用被装饰对象的方法前后添加自己的行为。这种链式结构在某种程度上类似于树的层次结构。
-
递归调用:装饰器模式中的行为可以通过递归调用来层层叠加。例如,如果你有多个装饰器,每个装饰器都会调用下一个装饰器的方法,直到最终调用到被装饰对象。这种递归调用的方式与树结构的遍历有相似之处。
-
动态组合:装饰器模式允许在运行时动态地组合多个装饰器,形成不同的功能组合。这种灵活性使得多个装饰器可以像树的分支一样,根据需要动态地组合在一起。
装饰器模式的UML类图
+-------------------+
| Component |
+-------------------+
| - operation() |
+-------------------+
^
|
+-------------------+
| ConcreteComponent|
+-------------------+
| - operation() |
+-------------------+
+-------------------+
| Decorator |
+-------------------+
| - component: Component |
| - operation() |
+-------------------+
^
|
+-------------------+ +-------------------+
| ConcreteDecoratorA | | ConcreteDecoratorB |
+-------------------+ +-------------------+
| - operation() | | - operation() |
+-------------------+ +-------------------+
详细说明
-
Component(组件接口):
- 定义了一个抽象接口,所有的具体组件和装饰器都必须实现这个接口。
- 方法:
operation()
- 定义了组件的基本行为。
-
ConcreteComponent(具体组件):
- 实现了Component接口,定义了具体组件的行为。
- 方法:
operation()
- 实现了具体的业务逻辑。
-
Decorator(装饰器抽象类):
- 继承自Component接口,持有一个Component类型的引用或指针,可以在运行时动态地为具体组件添加行为。
- 成员变量:
component
- 持有一个Component类型的引用或指针。 - 方法:
operation()
- 调用被装饰对象的operation()
方法,并可以在此方法前后添加新的行为。
-
ConcreteDecoratorA(具体装饰器A):
- 继承自Decorator,实现具体的装饰功能。
- 方法:
operation()
- 在调用被装饰对象的operation()
方法前后添加新的行为。
-
ConcreteDecoratorB(具体装饰器B):
- 继承自Decorator,实现具体的装饰功能。
- 方法:
operation()
- 在调用被装饰对象的operation()
方法前后添加新的行为。
示例代码
假设我们有一个为文本添加不同格式的装饰器模式实现,以下是详细的C++代码示例:
#include <iostream>
#include <string>
// Component(组件接口)
class TextComponent {
public:
virtual std::string getText() const = 0;
virtual ~TextComponent() {}
};
// ConcreteComponent(具体组件)
class PlainText : public TextComponent {
private:
std::string _text;
public:
PlainText(const std::string& text) : _text(text) {}
std::string getText() const override {
return _text;
}
};
// Decorator(装饰器抽象类)
class TextDecorator : public TextComponent {
protected:
TextComponent* _component;
public:
TextDecorator(TextComponent* component) : _component(component) {}
std::string getText() const override {
return _component->getText();
}
};
// ConcreteDecoratorA(具体装饰器A - 加粗)
class BoldText : public TextDecorator {
public:
BoldText(TextComponent* component) : TextDecorator(component) {}
std::string getText() const override {
return "<b>" + TextDecorator::getText() + "</b>";
}
};
// ConcreteDecoratorB(具体装饰器B - 斜体)
class ItalicText : public TextDecorator {
public:
ItalicText(TextComponent* component) : TextDecorator(component) {}
std::string getText() const override {
return "<i>" + TextDecorator::getText() + "</i>";
}
};
int main() {
// 创建一个具体组件
TextComponent* plainText = new PlainText("Hello, World!");
// 使用具体装饰器A(加粗)
TextComponent* boldText = new BoldText(plainText);
// 使用具体装饰器B(斜体)
TextComponent* italicBoldText = new ItalicText(boldText);
// 输出装饰后的文本
std::cout << italicBoldText->getText() << std::endl; // <i><b>Hello, World!</b></i>
// 释放内存
delete italicBoldText;
delete boldText;
delete plainText;
return 0;
}
UML类图
为了更直观地理解,以下是装饰器模式的UML类图:
+-------------------+
| TextComponent |
+-------------------+
| - getText(): string |
+-------------------+
^
|
+-------------------+
| PlainText |
+-------------------+
| - _text: string |
| - getText(): string |
+-------------------+
+-------------------+
| TextDecorator |
+-------------------+
| - _component: TextComponent |
| - getText(): string |
+-------------------+
^
|
+-------------------+ +-------------------+
| BoldText | | ItalicText |
+-------------------+ +-------------------+
| - _component: TextComponent | | - _component: TextComponent |
| - getText(): string | | - getText(): string |
+-------------------+ +-------------------+
各参与者及其职责
综合应用的灵活性
通过这种方式,你可以根据需要动态地组合多个装饰器,实现多种功能的组合。例如:
-
TextComponent(组件接口):
- 职责:定义了一个抽象接口,所有的具体组件和装饰器都必须实现这个接口。
- 方法:
getText() const
- 定义了组件的基本行为。
-
PlainText(具体组件):
- 职责:实现TextComponent接口,定义了具体组件的行为。
- 方法:
getText() const
- 返回未装饰的文本。
-
TextDecorator(装饰器抽象类):
- 职责:持有一个TextComponent类型的引用或指针,可以在运行时动态地为具体组件添加行为。
- 成员变量:
_component
- 持有一个TextComponent类型的引用或指针。 - 方法:
getText() const
- 调用被装饰对象的getText()
方法,并可以在此方法前后添加新的行为。
-
BoldText(具体装饰器A):
- 职责:继承自TextDecorator,实现具体的加粗装饰功能。
- 方法:
getText() const
- 在调用被装饰对象的getText()
方法前后添加加粗的HTML标签。
-
ItalicText(具体装饰器B):
- 职责:继承自TextDecorator,实现具体的斜体装饰功能。
- 方法:
getText() const
- 在调用被装饰对象的getText()
方法前后添加斜体的HTML标签。
-
以网络传输文本为例,我们将使用装饰器模式来实现对文本的压缩、加密及其他附加功能,并展示如何通过组合这些装饰器来实现多种功能的综合应用。
1. 定义Component接口
首先,我们定义一个抽象接口
TextComponent
,该接口包含一个sendText
方法,用于表示文本传输的基本行为。#include <iostream> #include <string> class TextComponent { public: virtual ~TextComponent() {} virtual std::string sendText() const = 0; };
2. 实现ConcreteComponent
然后,我们实现一个具体的组件
PlainText
,表示未经过任何处理的普通文本。class PlainText : public TextComponent { private: std::string _text; public: PlainText(const std::string& text) : _text(text) {} std::string sendText() const override { return _text; } };
3. 定义Decorator抽象类
装饰器抽象类
TextDecorator
也继承自TextComponent
,并且持有一个TextComponent
类型的引用或指针,用于装饰的具体对象。class TextDecorator : public TextComponent { protected: TextComponent* _component; public: TextDecorator(TextComponent* component) : _component(component) {} std::string sendText() const override { return _component->sendText(); } };
4. 实现具体装饰器
接下来,我们实现具体的装饰器类,分别为
CompressText
、EncryptText
和AddTimestampText
。压缩装饰器(CompressText)
class CompressText : public TextDecorator { public: CompressText(TextComponent* component) : TextDecorator(component) {} std::string sendText() const override { std::string text = TextDecorator::sendText(); // 假设有一个简单的压缩算法 std::string compressedText = compress(text); return compressedText; } private: std::string compress(const std::string& text) const { // 简单的压缩算法示例,这里只是返回压缩后的文本 return "compress(" + text + ")"; } };
加密装饰器(EncryptText)
class EncryptText : public TextDecorator { public: EncryptText(TextComponent* component) : TextDecorator(component) {} std::string sendText() const override { std::string text = TextDecorator::sendText(); // 假设有一个简单的加密算法 std::string encryptedText = encrypt(text); return encryptedText; } private: std::string encrypt(const std::string& text) const { // 简单的加密算法示例,这里只是返回加密后的文本 return "encrypt(" + text + ")"; } };
添加时间戳装饰器(AddTimestampText)
class AddTimestampText : public TextDecorator { public: AddTimestampText(TextComponent* component) : TextDecorator(component) {} std::string sendText() const override { std::string text = TextDecorator::sendText(); // 假设有一个时间戳生成函数 std::string timestamp = generateTimestamp(); return timestamp + " " + text; } private: std::string generateTimestamp() const { // 简单的时间戳生成示例 return "2024-12-24T11:34:34.221Z"; } };
5. 综合应用
现在,我们可以通过组合不同的装饰器来实现多种功能的综合应用。例如,我们可以先压缩文本,再加密,最后添加时间戳。
int main() { // 创建一个具体组件 TextComponent* plainText = new PlainText("Hello, World!"); // 使用压缩装饰器 TextComponent* compressedText = new CompressText(plainText); // 使用加密装饰器 TextComponent* encryptedText = new EncryptText(compressedText); // 使用添加时间戳装饰器 TextComponent* timestampedText = new AddTimestampText(encryptedText); // 输出最终处理后的文本 std::cout << timestampedText->sendText() << std::endl; // 2024-12-24T11:34:34.221Z encrypt(compress(Hello, World!)) // 释放内存 delete timestampedText; delete encryptedText; delete compressedText; delete plainText; return 0; }
代码解释
- PlainText:这是具体组件,实现了
sendText
方法,返回未经过任何处理的普通文本。 - TextDecorator:这是装饰器抽象类,持有一个
TextComponent
类型的引用,并实现了sendText
方法,调用被装饰对象的sendText
方法。 - CompressText:实现了一个具体的压缩装饰器,调用被装饰对象的
sendText
方法后,对返回的文本进行压缩处理。 - EncryptText:实现了一个具体的加密装饰器,调用被装饰对象的
sendText
方法后,对返回的文本进行加密处理。 - AddTimestampText:实现了一个具体的添加时间戳装饰器,调用被装饰对象的
sendText
方法后,对返回的文本添加时间戳。 - 仅压缩:
new CompressText(new PlainText("Hello, World!"))
- 仅加密:
new EncryptText(new PlainText("Hello, World!"))
- 仅添加时间戳:
new AddTimestampText(new PlainText("Hello, World!"))
- 压缩和加密:
new EncryptText(new CompressText(new PlainText("Hello, World!")))
- 加密和添加时间戳:
new AddTimestampText(new EncryptText(new PlainText("Hello, World!")))
装饰器模式、策略模式和Visitor模式的相似之处和不同之处
1. 相似之处
- 动态变化:这三种模式都允许在对象创建后动态地改变其行为。
- 组合简化:它们都通过组合而不是继承来实现行为的改变,从而避免了类的爆炸。
- 行为扩展:这三种模式都提供了一种方便的方式来扩展对象的行为,而不需要修改现有的类。
2. 不同之处
装饰器模式(Decorator Pattern)
- 意图:装饰器模式旨在不改变对象接口的情况下动态地为其添加职责或行为。它通过组合的方式将多个装饰器对象串联起来,形成一个层次结构。
- 主要参与者:
- Component:定义了一个对象接口,可以动态地为其添加职责。
- ConcreteComponent:实现了Component接口,定义了具体对象的行为。
- Decorator:持有一个Component类型的引用,可以在调用被装饰对象的方法前后添加新的行为。
- ConcreteDecorator:实现了具体的装饰功能。
- 适用场合:
- 需要在不改变对象接口的情况下动态地添加功能。
- 避免使用继承导致的类爆炸问题。
- 需要多个独立的功能可以任意组合。
策略模式(Strategy Pattern)
- 意图:策略模式旨在定义一系列算法(策略),将每个算法封装起来,并使它们可以互换。客户端可以动态地选择并使用不同的策略。
- 主要参与者:
- Strategy:定义了所有支持的算法的公共接口。
- ConcreteStrategy:实现了具体的算法。
- Context:持有一个Strategy类型的引用,通过这个引用来调用策略对象的方法。
- 适用场合:
- 需要在运行时选择不同的算法或行为。
- 需要将算法的定义和使用分离,以增强代码的灵活性和可维护性。
- 有多个类似的行为需要根据不同的条件来选择。
Visitor模式(Visitor Pattern)
- 意图:Visitor模式旨在表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义新的操作。
- 主要参与者:
- Element:定义了一个接受访问者的接口,通常是一个
accept(Visitor*)
方法。 - ConcreteElement:实现了
accept(Visitor*)
方法,通常会调用访问者的访问方法。 - Visitor:定义了一个访问Element的接口,通常是一个
visit(ConcreteElement*)
方法。 - ConcreteVisitor:实现了具体的访问操作。
- ObjectStructure:可以枚举或遍历元素,提供一个高层次的接口让访问者访问元素。
- Element:定义了一个接受访问者的接口,通常是一个
- 适用场合:
- 需要在不改变对象结构的前提下,为对象结构中的元素添加新的操作。
- 有多个操作需要对对象结构中的元素进行,而且这些操作之间有共通的行为。
- 对象结构相对稳定,但需要频繁添加新的操作。
详细对比
动态变化
- 装饰器模式:通过组合多个装饰器对象动态地添加职责。
- 策略模式:通过在运行时选择不同的策略对象来改变行为。
- Visitor模式:通过在运行时选择不同的访问者对象来添加新的操作。
组合简化
- 装饰器模式:通过装饰器对象的层次结构来组合功能。
- 策略模式:通过策略对象的集合来选择和组合算法。
- Visitor模式:通过访问者对象的集合来组合操作,但不改变元素对象的结构。
行为扩展
- 装饰器模式:在对象的原有行为基础上添加新的行为,通常是在方法调用前后执行额外的操作。
- 策略模式:提供了一组可互换的算法,客户端可以在运行时选择使用哪个算法。
- Visitor模式:为对象结构中的多个元素定义新的操作,而不改变这些元素的类。
适用场合对比
装饰器模式
- 动态添加功能:最适用于在不修改对象接口的情况下动态地添加功能。
- 避免类爆炸:避免通过继承添加功能导致的类的爆炸。
- 多层次装饰:适合需要多个独立功能可以任意组合的场合。
策略模式
- 算法互换:最适合需要在运行时选择不同算法或行为的场合。
- 行为分离:适合将算法的定义和使用分离,增强代码的灵活性和可维护性。
- 条件选择:适合有多个类似行为需要根据不同条件选择的场合。
Visitor模式
- 添加新操作:最适合在不改变对象结构的前提下为对象结构中的多个元素添加新的操作。
- 稳定的对象结构:适合对象结构相对稳定,但需要频繁添加新操作的场合。
- 集中处理:适合需要集中处理多个元素的不同操作,而这些操作可能有共通部分。
总结
- 装饰器模式主要用于扩展对象的功能,通过组合而不是继承来实现动态的行为变化。
- 策略模式主要用于在运行时选择不同的算法或行为,通过策略对象的互换来实现行为的变化。
- Visitor模式主要用于在不改变对象结构的前提下为对象结构中的多个元素添加新的操作,通过访问者对象来实现新的行为。
这三种模式虽然在某些方面有相似之处,但它们的意图和适用场合各不相同,选择哪种模式取决于具体的设计需求和问题背景。