装饰模式(Decorator Pattern)是一种结构型设计模式,它允许用户通过将对象包装进装饰类中以扩展其功能,而不改变原有类的使用。这种模式特别有用于遵守开闭原则的场景,即系统应对扩展开放,对修改关闭。
下面是一个使用C++实现装饰模式的示例,其中展示了如何为一个简单的饮料类添加不同的调料装饰:
第一步:定义饮料基类
// Beverage.h
#pragma once
#include <string>
class Beverage {
public:
virtual ~Beverage() = default;
virtual std::string getDescription() const = 0;
virtual double cost() const = 0;
};
第二步:实现具体饮料
// Coffee.h
#include "Beverage.h"
class Coffee : public Beverage {
public:
std::string getDescription() const override {
return "Coffee";
}
double cost() const override {
return 1.99;
}
};
第三步:定义装饰者基类
装饰者和被装饰对象有相同的基类,使得装饰者可以代替被装饰对象。
// CondimentDecorator.h
#include "Beverage.h"
class CondimentDecorator : public Beverage {
public:
virtual std::string getDescription() const = 0;
};
第四步:实现具体的装饰者
// Milk.h
#include "CondimentDecorator.h"
class Milk : public CondimentDecorator {
const Beverage* beverage; // 被装饰的饮料
public:
Milk(const Beverage* beverage) : beverage(beverage) {}
std::string getDescription() const override {
return beverage->getDescription() + ", Milk";
}
double cost() const override {
return .50 + beverage->cost();
}
};
// Sugar.h
#include "CondimentDecorator.h"
class Sugar : public CondimentDecorator {
const Beverage* beverage;
public:
Sugar(const Beverage* beverage) : beverage(beverage) {}
std::string getDescription() const override {
return beverage->getDescription() + ", Sugar";
}
double cost() const override {
return .20 + beverage->cost();
}
};
第五步:使用装饰者模式
#include <iostream>
#include "Coffee.h"
#include "Milk.h"
#include "Sugar.h"
int main() {
Beverage* coffee = new Coffee();
std::cout << coffee->getDescription() << " $" << coffee->cost() << std::endl;
Beverage* coffeeWithMilk = new Milk(coffee);
std::cout << coffeeWithMilk->getDescription() << " $" << coffeeWithMilk->cost() << std::endl;
Beverage* coffeeWithMilkAndSugar = new Sugar(coffeeWithMilk);
std::cout << coffeeWithMilkAndSugar->getDescription() << " $" << coffeeWithMilkAndSugar->cost() << std::endl;
// 清理资源
delete coffeeWithMilkAndSugar;
delete coffeeWithMilk;
delete coffee;
return 0;
}
在这个示例中,Beverage
是所有饮料的基类,Coffee
是具体的饮料,而Milk
和Sugar
是装饰者,它们可以动态地添加到Coffee
对象上,而不改变Coffee
类的代码。使用装饰者模式,可以灵活地组合各种装饰,以扩展对象的功能。
1.主要优点
装饰模式的主要优点如下:(1)对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。(2)可以通过一种动态的方式来扩展一个对象的功能。通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为。(3)可以对一个对象进行多次装饰。通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。(4)具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合开闭原则。
2.主要缺点
装饰模式的主要缺点如下:(1)使用装饰模式进行系统设计时将产生很多小对象。这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同。大量小对象的产生势必会占用更多的系统资源,在一定程度上影响程序的性能。(2)装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难。对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
3.适用场景
在以下情况下可以考虑使用装饰模式:(1)在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。(2)当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。不能采用继承的情况主要有两类:第1类是系统中存在大量独立的扩展,为支持每一种扩展或者扩展之间的组合将产生大量的子类,使得子类数目呈爆炸性增长;第2类是因为类已定义为不能被继承(如Java语言中的final类)。
装饰模式与组合模式联合使用
下面是一个装饰模式与组合模式联合使用的案例
在此案例中,我们将结合使用装饰模式和组合模式来实现一个文本编辑器的功能,其中可以添加和组合不同的文本样式(如粗体、斜体)和结构(如段落、列表)。装饰模式用于动态地给文本添加样式,而组合模式允许我们以树形结构组合文本元素,比如段落内可以嵌套列表。
定义文本组件和装饰者基类
// TextComponent.h
#pragma once
#include <string>
#include <iostream>
#include <vector>
class TextComponent {
public:
virtual ~TextComponent() = default;
virtual void display() const = 0;
};
class TextDecorator : public TextComponent {
protected:
const TextComponent* component;
public:
TextDecorator(const TextComponent* component) : component(component) {}
virtual void display() const override {
component->display();
}
};
实现具体的文本元素
// SimpleText.h
#include "TextComponent.h"
class SimpleText : public TextComponent {
std::string text;
public:
SimpleText(const std::string& text) : text(text) {}
void display() const override {
std::cout << text;
}
};
实现具体的装饰者
// BoldDecorator.h
#include "TextComponent.h"
class BoldDecorator : public TextDecorator {
public:
BoldDecorator(const TextComponent* component) : TextDecorator(component) {}
void display() const override {
std::cout << "<b>";
TextDecorator::display();
std::cout << "</b>";
}
};
// ItalicDecorator.h
#include "TextComponent.h"
class ItalicDecorator : public TextDecorator {
public:
ItalicDecorator(const TextComponent* component) : TextDecorator(component) {}
void display() const override {
std::cout << "<i>";
TextDecorator::display();
std::cout << "</i>";
}
};
实现组合模式
// CompositeText.h
#include "TextComponent.h"
class CompositeText : public TextComponent {
std::vector<const TextComponent*> children;
public:
void add(const TextComponent* component) {
children.push_back(component);
}
void display() const override {
for (auto* child : children) {
child->display();
std::cout << std::endl; // New line for each component for clarity
}
}
};
使用装饰模式和组合模式
int main() {
CompositeText document;
SimpleText simpleText("Hello, World!");
BoldDecorator boldText(&simpleText);
ItalicDecorator italicText(&boldText);
document.add(&simpleText);
document.add(&boldText);
document.add(&italicText);
CompositeText list;
SimpleText listItem1("Item 1");
SimpleText listItem2("Item 2");
list.add(&listItem1);
list.add(&listItem2);
document.add(&list);
document.display();
return 0;
}
在这个示例中,SimpleText
代表简单的文本元素,BoldDecorator
和ItalicDecorator
是装饰者,用于给文本添加粗体和斜体样式。CompositeText
是组合对象,允许我们组合多个TextComponent
对象,包括另一个CompositeText
,如一个列表。通过这种方式,我们可以构建复杂的文本结构,并且灵活地添加样式。
装饰模式与适配器模式
装饰模式(Decorator Pattern)和适配器模式(Adapter Pattern)都属于结构型设计模式,在软件开发中用于解决对象组合和通信的问题,但它们解决的问题类型、目的和实现方式有所不同。
装饰模式
- 目的:装饰模式的主要目的是在不改变对象接口的情况下,动态地给对象添加新的功能。它主要通过创建一个包装对象(即装饰者)来扩展目标对象的行为,而不是继承目标类。
- 用法:当你需要添加的功能可以动态地撤销或组合时,装饰模式是非常有用的。例如,你可以使用装饰模式来动态地给视觉组件添加边框、滚动条等附加功能。
- 实现:装饰模式通过创建一个装饰类,该类包含一个指向被装饰对象的引用。所有装饰者和被装饰对象都应该继承自同一个接口或基类,确保装饰者可以替代被装饰者使用。
适配器模式
- 目的:适配器模式的主要目的是允许不兼容的接口之间进行通信,即使它们不能直接连接也能让它们一起工作。它通过将一个类的接口转换成客户端所期望的另一个接口来实现。
- 用法:当你希望使用某个现有类,但是其接口与你的其他代码不兼容时,可以使用适配器模式。例如,当一个第三方库提供了丰富的功能,但是其接口与你的系统不匹配时,适配器可以解决这个问题。
- 实现:适配器模式通常通过创建一个新的适配器类来实现,这个类包含一个对现有类的引用,并实现了目标接口。适配器类转换(或包装)现有类的调用到目标接口格式。
区别总结
- 目的不同:装饰模式主要用于添加新功能,而适配器模式主要用于解决接口不兼容的问题。
- 用法场景:装饰模式适用于功能的动态扩展,适配器模式适用于接口的适配。
- 实现方式:虽然两者都可能使用组合来实现目的,但装饰模式强调在保持接口不变的情况下增加职责,适配器模式强调转换接口以适应不同的需求。