装饰模式(Decorator Pattern)是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。装饰模式通过创建一个装饰类来包裹原始类,并在保留原始类接口的同时,扩展其功能。
使用场景
-
在不修改现有类的情况下扩展功能
当需要向一个类添加新的功能,但不希望修改现有类或通过继承的方式来扩展时,可以使用装饰模式。示例:在一个图形编辑器中,想为基本图形对象(如矩形、圆形)添加边框、填充颜色等功能,可以使用装饰模式来动态添加这些功能。
-
动态组合功能
当需要动态地组合对象的多个功能,而这些功能可以被独立地添加或移除时,可以使用装饰模式。示例:在一个文本处理系统中,可以为文本添加不同的格式(如加粗、斜体、下划线),这些格式可以组合使用。
-
替代继承
当使用继承来扩展功能会导致类的数量急剧增加且难以维护时,可以使用装饰模式来替代继承。示例:在一个文件输入输出系统中,可以为基本的文件流添加缓冲、加密、压缩等功能,而不需要为每种组合创建新的子类。
UML类图
+-------------------+ +-------------------+
| Component |<------| Decorator |
+-------------------+ +-------------------+
| +operation() | | -component: Component |
+-------------------+ | +operation() |
+-------------------+
/|\
|
|
+-------------------+
| ConcreteDecorator|
+-------------------+
| +operation() |
+-------------------+
/|\
|
|
+-------------------+
| ConcreteDecoratorA|
+-------------------+
| +operation() |
+-------------------+
类图解释
- Component:抽象组件类,定义了一个接口,所有具体组件和装饰类都需要实现这个接口。
- ConcreteComponent:具体组件类,实现了
Component
接口,提供基本的操作。 - Decorator:抽象装饰类,继承自
Component
,并持有一个Component
对象的引用。 - **ConcreteDecoratorA **:具体装饰类,继承自
Decorator
,在基本操作的基础上添加新的功能。
示例代码
假设我们有一个文本编辑器,基本的文本可以通过装饰模式动态地添加不同的格式(如加粗、斜体、下划线)。
#include <iostream>
#include <memory>
#include <string>
// 抽象组件类
class Text {
public:
virtual std::string getText() const = 0;
virtual ~Text() = default;
};
// 具体组件类
class PlainText : public Text {
public:
PlainText(const std::string& text) : text_(text) {}
std::string getText() const override {
return text_;
}
private:
std::string text_;
};
// 抽象装饰类
class TextDecorator : public Text {
public:
TextDecorator(std::unique_ptr<Text> text) : text_(std::move(text)) {}
protected:
std::unique_ptr<Text> text_;
};
// 具体装饰类:加粗
class BoldText : public TextDecorator {
public:
BoldText(std::unique_ptr<Text> text) : TextDecorator(std::move(text)) {}
std::string getText() const override {
return "<b>" + text_->getText() + "</b>";
}
};
// 具体装饰类:斜体
class ItalicText : public TextDecorator {
public:
ItalicText(std::unique_ptr<Text> text) : TextDecorator(std::move(text)) {}
std::string getText() const override {
return "<i>" + text_->getText() + "</i>";
}
};
// 具体装饰类:下划线
class UnderlineText : public TextDecorator {
public:
UnderlineText(std::unique_ptr<Text> text) : TextDecorator(std::move(text)) {}
std::string getText() const override {
return "<u>" + text_->getText() + "</u>";
}
};
// 客户端代码
int main() {
std::unique_ptr<Text> myText = std::make_unique<PlainText>("Hello, world!");
myText = std::make_unique<BoldText>(std::move(myText));
myText = std::make_unique<ItalicText>(std::move(myText));
myText = std::make_unique<UnderlineText>(std::move(myText));
std::cout << myText->getText() << std::endl;
return 0;
}
代码解读
- 抽象组件类(Text):定义了一个获取文本的方法,所有具体组件和装饰类都需要实现这个方法。
- 具体组件类(PlainText):实现了
Text
接口,提供基本的文本内容。 - 抽象装饰类(TextDecorator):继承自
Text
,并持有一个Text
对象的指针,装饰类通过这个指针来调用基本的文本操作。 - 具体装饰类(BoldText、ItalicText、UnderlineText):继承自
TextDecorator
,在基本文本的基础上添加不同的格式(如加粗、斜体、下划线)。 - 客户端代码:通过具体组件和装饰类的组合,动态地为文本添加不同的格式。
优点
-
遵循开闭原则:
可以在不修改现有类的情况下,动态地添加新的功能。 -
更灵活的功能组合:
可以通过不同的装饰类组合,动态地添加或移除功能。 -
替代继承:
可以避免通过继承来扩展功能,减少子类数量,降低系统复杂性。 -
细化对象职责:
每个装饰类只负责一个功能,职责单一,便于理解和维护。
缺点
-
增加复杂性:
过多的装饰类和嵌套调用会增加系统复杂性,理解和调试变得困难。 -
可能影响性能:
装饰模式会增加大量的小对象,并且嵌套调用增加了调用链的长度,可能会影响性能。 -
排错困难:
在出现问题时,可能很难追踪到是哪个装饰类引起的问题,因为调用链较长。
使用场景总结
- 在不修改现有类的情况下扩展功能:如为图形对象添加边框、填充颜色等。
- 动态组合功能:如为文本添加不同的格式(加粗、斜体、下划线)。
- 替代继承:如为文件流添加缓冲、加密、压缩等功能。
- 细化对象职责:每个装饰类只负责一个功能,职责单一。
通过这些示例和解释,可以看到装饰模式在扩展对象功能、动态组合功能和替代继承方面的强大功能。