设计模式-装饰器模式
基础概念介绍
代码实现
基础模式
基础模式实现一
如果不使用装饰器模式,而是采用最普遍的类继承和组合的方式,我们需要直接在每个具体类中添加新的功能。这可能导致类的层次结构变得复杂,难以维护,因为每个具体类都需要预先知道所有可能的组合方式。
下面是一个简化的示例,演示了使用类继承和组合的方式来实现相同的功能:
coffee_simple.hpp (头文件)
#ifndef COFFEE_SIMPLE_HPP
#define COFFEE_SIMPLE_HPP
#include <iostream>
#include <string>
// 基础咖啡类
class SimpleCoffeeSimple {
public:
std::string getDescription() const {
return "Simple Coffee";
}
double cost() const {
return 1.0;
}
};
// 咖啡加牛奶类
class CoffeeWithMilkSimple {
SimpleCoffeeSimple baseCoffee;
public:
std::string getDescription() const {
return baseCoffee.getDescription() + ", Milk";
}
double cost() const {
return baseCoffee.cost() + 0.5;
}
};
// 咖啡加牛奶和糖类
class CoffeeWithMilkAndSugarSimple {
CoffeeWithMilkSimple baseCoffee;
public:
std::string getDescription() const {
return baseCoffee.getDescription() + ", Sugar";
}
double cost() const {
return baseCoffee.cost() + 0.2;
}
};
#endif // COFFEE_SIMPLE_HPP
main_simple.cpp (主函数调用)
#include "coffee_simple.hpp"
int main() {
SimpleCoffeeSimple simpleCoffee;
std::cout << "Description: " << simpleCoffee.getDescription() << ", Cost: $" << simpleCoffee.cost() << std::endl;
CoffeeWithMilkSimple coffeeWithMilk;
std::cout << "Description: " << coffeeWithMilk.getDescription() << ", Cost: $" << coffeeWithMilk.cost() << std::endl;
CoffeeWithMilkAndSugarSimple coffeeWithMilkAndSugar;
std::cout << "Description: " << coffeeWithMilkAndSugar.getDescription() << ", Cost: $" << coffeeWithMilkAndSugar.cost() << std::endl;
return 0;
}
这个简单的例子中,我们创建了基础的咖啡类 SimpleCoffeeSimple,以及添加了牛奶和糖的两个具体类。每个类中都包含了 getDescription 和 cost 函数,用于描述咖啡的属性和计算成本。
基础模式实现二
coffee_no_decorator.hpp (头文件)
#ifndef COFFEE_NO_DECORATOR_HPP
#define COFFEE_NO_DECORATOR_HPP
#include <iostream>
#include <string>
// Component(组件):定义一个对象接口,可以动态地给该对象添加一些职责
class CoffeeNoDecorator {
public:
virtual ~CoffeeNoDecorator() = default;
virtual std::string getDescription() const = 0;
virtual double cost() const = 0;
virtual void serve() const {
std::cout << "Serving " << getDescription() << " coffee." << std::endl;
}
};
// 具体组件:SimpleCoffee
class SimpleCoffeeNoDecorator : public CoffeeNoDecorator {
public:
std::string getDescription() const override {
return "Simple Coffee";
}
double cost() const override {
return 1.0;
}
};
// 调料组合类:CoffeeWithMilkAndSugar
class CoffeeWithMilkAndSugarNoDecorator : public CoffeeNoDecorator {
SimpleCoffeeNoDecorator baseCoffee;
public:
std::string getDescription() const override {
return baseCoffee.getDescription() + ", Milk, Sugar";
}
double cost() const override {
return baseCoffee.cost() + 0.5 + 0.2;
}
};
#endif // COFFEE_NO_DECORATOR_HPP
coffee_no_decorator.cpp (类实现)
#include "coffee_no_decorator.hpp"
// 没有新的功能需要添加,因此这里的实现相对简单
main_no_decorator.cpp (主函数调用)
#include "coffee_no_decorator.hpp"
int main() {
// 基础咖啡
CoffeeNoDecorator* simpleCoffee = new SimpleCoffeeNoDecorator();
simpleCoffee->serve();
delete simpleCoffee;
// 咖啡加牛奶和糖
CoffeeNoDecorator* sugarMilkCoffee = new CoffeeWithMilkAndSugarNoDecorator();
sugarMilkCoffee->serve();
delete sugarMilkCoffee;
return 0;
}
CMakeLists_no_decorator.txt
cmake_minimum_required(VERSION 3.10)
project(CoffeeNoDecoratorPattern)
# 添加头文件目录
include_directories(${CMAKE_SOURCE_DIR})
# 添加可执行文件
add_executable(coffee_no_decorator main_no_decorator.cpp coffee_no_decorator.cpp)
# 指定C++标准
set(CMAKE_CXX_STANDARD 11)
# 指定生成目标链接的库
target_link_libraries(coffee_no_decorator)
在这个实现中,我们创建了两个类:SimpleCoffeeNoDecorator 表示基础咖啡,CoffeeWithMilkAndSugarNoDecorator 表示加了牛奶和糖的咖啡。这两个类通过继承和组合来实现不同组合的咖啡。
通过比较装饰器模式和不使用装饰器模式的实现:
使用装饰器模式的优点:
灵活性和可扩展性: 装饰器模式允许动态地组合各种功能,而不影响原始类的结构。新功能可以轻松添加,而不需要修改现有代码。
遵循开闭原则: 装饰器模式使得系统更容易扩展,避免了通过继承创建复杂的类层次结构。
不使用装饰器模式的劣势:
类层次结构复杂: 不使用装饰器模式可能导致类层次结构的复杂性增加,特别是在有多个组合的情况下。
类的修改: 如果需要添加新的功能,可能需要直接修改现有的类,这违反了开闭原则,可能导致系统更难维护和扩展。
总体来说,使用装饰器模式能够更好地满足开闭原则,提高代码的灵活性和可维护性。
装饰器模式改进
简单装饰器模式
coffee.hpp (头文件)
#ifndef COFFEE_HPP
#define COFFEE_HPP
#include <iostream>
#include <string>
// Component(组件):定义一个对象接口,可以动态地给该对象添加一些职责
class Coffee {
public:
virtual ~Coffee() = default;
virtual std::string getDescription() const = 0;
virtual double cost() const = 0;
};
// ConcreteComponent(具体组件):定义一个具体的对象,也可以给这个对象添加一些职责
class SimpleCoffee : public Coffee {
public:
std::string getDescription() const override {
return "Simple Coffee";
}
double cost() const override {
return 1.0;
}
};
// Decorator(装饰器):维持一个指向Component对象的指针,并定义一个与Component接口一致的接口
class CoffeeDecorator : public Coffee {
protected:
Coffee* coffee;
public:
CoffeeDecorator(Coffee* c) : coffee(c) {}
std::string getDescription() const override {
return coffee->getDescription();
}
double cost() const override {
return coffee->cost();
}
};
// ConcreteDecorator(具体装饰器):具体的装饰器,向组件添加职责
class MilkDecorator : public CoffeeDecorator {
public:
MilkDecorator(Coffee* c) : CoffeeDecorator(c) {}
std::string getDescription() const override {
return coffee->getDescription() + ", Milk";
}
double cost() const override {
return coffee->cost() + 0.5;
}
};
class SugarDecorator : public CoffeeDecorator {
public:
SugarDecorator(Coffee* c) : CoffeeDecorator(c) {}
std::string getDescription() const override {
return coffee->getDescription() + ", Sugar";
}
double cost() const override {
return coffee->cost() + 0.2;
}
};
#endif // COFFEE_HPP
coffee.cpp (类实现)
#include "coffee.hpp"
// 可以在这里添加额外的实现,但在这个例子中,我们的类已经足够简单,无需额外的实现。
main.cpp (主函数调用)
#include "coffee.hpp"
int main() {
Coffee* myCoffee = new SimpleCoffee();
std::cout << "Description: " << myCoffee->getDescription() << ", Cost: $" << myCoffee->cost() << std::endl;
myCoffee = new MilkDecorator(myCoffee);
std::cout << "Description: " << myCoffee->getDescription() << ", Cost: $" << myCoffee->cost() << std::endl;
myCoffee = new SugarDecorator(myCoffee);
std::cout << "Description: " << myCoffee->getDescription() << ", Cost: $" << myCoffee->cost() << std::endl;
delete myCoffee;
return 0;
}
这种组织方式将代码拆分为头文件、类实现和主函数调用的三个部分,更符合典型的C++项目结构,使代码更易读、易维护。如果项目规模增大,可以更方便地管理和扩展。
当你组织了多个源文件时,CMakeLists.txt 文件会有所变化。以下是修改后的 CMakeLists.txt 文件,适用于这个多文件的示例:
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(CoffeeDecoratorPattern)
# 添加头文件目录
include_directories(${CMAKE_SOURCE_DIR})
# 添加可执行文件
add_executable(coffee_decorator main.cpp coffee.cpp)
# 指定C++标准
set(CMAKE_CXX_STANDARD 11)
# 指定生成目标链接的库
target_link_libraries(coffee_decorator)
在这个 CMakeLists.txt 文件中,我使用 include_directories(${CMAKE_SOURCE_DIR}) 添加了头文件目录,确保 CMake 可以找到 coffee.hpp 头文件。然后,我使用 add_executable 命令将 main.cpp 和 coffee.cpp 编译成一个可执行文件 coffee_decorator。
要编译和运行代码,你可以按照之前提供的步骤进行。在包含代码和 CMakeLists.txt 文件的目录中,执行以下命令:
mkdir build # 创建一个build目录用于存放生成的文件
cd build # 进入build目录
# 使用CMake生成Makefile
cmake ..
# 使用Make编译项目
make
# 运行生成的可执行文件
./coffee_decorator
复杂装饰器模式
当使用装饰器模式时,我们的目标是在不修改基础类的情况下,动态地添加功能。为了更好地说明这一点,我们将考虑一个更实际的场景,例如咖啡馆系统,其中 SimpleCoffee 表示基础咖啡,而装饰器用于添加额外的功能,比如添加调料、改变咖啡温度等。
coffee.hpp (头文件)
#ifndef COFFEE_HPP
#define COFFEE_HPP
#include <iostream>
#include <string>
// Component(组件):定义一个对象接口,可以动态地给该对象添加一些职责
class Coffee {
public:
virtual ~Coffee() = default;
virtual std::string getDescription() const = 0;
virtual double cost() const = 0;
virtual void serve() const {
std::cout << "Serving " << getDescription() << " coffee." << std::endl;
}
};
// ConcreteComponent(具体组件):定义一个具体的对象,也可以给这个对象添加一些职责
class SimpleCoffee : public Coffee {
public:
std::string getDescription() const override {
return "Simple Coffee";
}
double cost() const override {
return 1.0;
}
};
// Decorator(装饰器):维持一个指向Component对象的指针,并定义一个与Component接口一致的接口
class CoffeeDecorator : public Coffee {
protected:
Coffee* coffee;
public:
CoffeeDecorator(Coffee* c) : coffee(c) {}
std::string getDescription() const override {
return coffee->getDescription();
}
double cost() const override {
return coffee->cost();
}
};
// 具体装饰器类,比如调料
class MilkDecorator : public CoffeeDecorator {
public:
MilkDecorator(Coffee* c) : CoffeeDecorator(c) {}
std::string getDescription() const override {
return coffee->getDescription() + ", Milk";
}
double cost() const override {
return coffee->cost() + 0.5;
}
};
class SugarDecorator : public CoffeeDecorator {
public:
SugarDecorator(Coffee* c) : CoffeeDecorator(c) {}
std::string getDescription() const override {
return coffee->getDescription() + ", Sugar";
}
double cost() const override {
return coffee->cost() + 0.2;
}
};
#endif // COFFEE_HPP
coffee.cpp (类实现)
#include "coffee.hpp"
// 具体的装饰器类,比如调整咖啡温度的装饰器
class TemperatureDecorator : public CoffeeDecorator {
public:
TemperatureDecorator(Coffee* c) : CoffeeDecorator(c) {}
std::string getDescription() const override {
return coffee->getDescription() + ", Hot";
}
double cost() const override {
return coffee->cost() + 0.3;
}
void serve() const override {
std::cout << "Serving " << getDescription() << " coffee." << std::endl;
}
};
// 具体的装饰器类,比如添加奶泡的装饰器
class FoamDecorator : public CoffeeDecorator {
public:
FoamDecorator(Coffee* c) : CoffeeDecorator(c) {}
std::string getDescription() const override {
return coffee->getDescription() + ", Foam";
}
double cost() const override {
return coffee->cost() + 0.4;
}
void serve() const override {
std::cout << "Serving " << getDescription() << " coffee." << std::endl;
}
};
// 具体的咖啡类,比如浓缩咖啡
class Espresso : public Coffee {
public:
std::string getDescription() const override {
return "Espresso";
}
double cost() const override {
return 2.0;
}
};
main.cpp (主函数调用)
#include "coffee.hpp"
int main() {
// 基础咖啡
Coffee* simpleCoffee = new SimpleCoffee();
simpleCoffee->serve();
delete simpleCoffee;
// 咖啡加牛奶
Coffee* milkCoffee = new MilkDecorator(new SimpleCoffee());
milkCoffee->serve();
delete milkCoffee;
// 咖啡加牛奶和糖
Coffee* sugarMilkCoffee = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
sugarMilkCoffee->serve();
delete sugarMilkCoffee;
// 咖啡加糖、牛奶和泡沫,这里使用了温度装饰器和奶泡装饰器
Coffee* complexCoffee = new FoamDecorator(new TemperatureDecorator(new SugarDecorator(new MilkDecorator(new SimpleCoffee()))));
complexCoffee->serve();
delete complexCoffee;
// 浓缩咖啡,这里使用了温度装饰器和奶泡装饰器
Coffee* espresso = new FoamDecorator(new TemperatureDecorator(new Espresso()));
espresso->serve();
delete espresso;
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(CoffeeDecoratorPattern)
# 添加头文件目录
include_directories(${CMAKE_SOURCE_DIR})
# 添加可执行文件
add_executable(coffee_decorator main.cpp coffee.cpp)
# 指定C++标准
set(CMAKE_CXX_STANDARD 11)
# 指定生成目标链接的库
target_link_libraries(coffee_decorator)
在这个例子中,我们引入了两个新的装饰器类:TemperatureDecorator(温度装饰器)和 FoamDecorator(奶泡装饰器),以及一个新的具体咖啡类 Espresso(浓缩咖啡)。这些类都实现了 Coffee 接口,通过装饰器模式,我们可以在运行时动态地为咖啡添加不同的功能,而无需修改原有代码。
通过对比简化例子和这个更复杂的例子,我们可以看到使用装饰器模式的优势,特别是在处理复杂组合的情况下。它使得系统更加灵活,易于扩展,同时遵循开闭原则。
使用装饰器模式的详细解释
- 基础咖啡类SimpleCoffee:
- SimpleCoffee 是具体组件,实现了 Coffee 接口。
- getDescription 返回 “Simple Coffee”,cost 返回基础咖啡的价格。
- 装饰器抽象类 CoffeeDecorator:
- CoffeeDecorator 是装饰器的抽象基类,也实现了 Coffee 接口。
- 拥有一个指向 Coffee 对象的指针,通过构造函数接受被装饰的咖啡对象。
- 具体装饰器类 MilkDecorator 和 SugarDecorator:
- 继承自 CoffeeDecorator,实现了 getDescription 和 cost 函数,以添加额外的功能。
- MilkDecorator 添加了牛奶,SugarDecorator 添加了糖。
- 主函数调用:
- 创建基础咖啡对象 SimpleCoffee,然后通过装饰器依次添加牛奶和糖。
- 输出咖啡的描述和成本。
- 新增功能的装饰器 TemperatureDecorator 和 FoamDecorator:
- TemperatureDecorator 添加了温度的装饰,FoamDecorator 添加了奶泡的装饰。
- 这些装饰器可以动态地为咖啡对象添加新的功能,而不影响原有类的结构。
- 新的具体咖啡类 Espresso:
- Espresso 是一个新的具体咖啡类,实现了 Coffee 接口。
- 输出描述为 “Espresso”,成本为 2.0。
- 主函数调用中新增复杂组合的咖啡:
- 创建一个复杂的咖啡,包括糖、牛奶、温度装饰、奶泡装饰等。
- 输出咖啡的描述和成本。
不使用装饰器模式的对比:
- 基础咖啡类 SimpleCoffeeSimple:
- SimpleCoffeeSimple 只是一个简单的咖啡类,没有考虑添加额外功能的可能性。
- 每个组合的咖啡类:
- CoffeeWithMilkSimple、CoffeeWithMilkAndSugarSimple 等都是通过直接继承和组合来实现的。
- 每个新的组合都需要创建一个新的类,导致类的层次结构不断扩展。
- 温度和奶泡的功能:
- 如果要添加温度或奶泡的功能,可能需要修改每个具体类,导致类的修改。
- 可扩展性和灵活性:
- 不使用装饰器模式,类的层次结构可能变得复杂,且不容易扩展。
- 添加新的功能可能需要修改现有类,违反了开闭原则。
- 代码重复:
- 针对每个具体咖啡,可能需要重复实现相似的功能,导致代码冗余。
通过比较可以看到,使用装饰器模式的优势在于系统的灵活性和可扩展性。在不使用装饰器模式的情况下,可能会导致类层次结构的不断扩展,代码变得难以维护和扩展。而装饰器模式允许在运行时动态地添加新功能,而无需修改原有类的结构,使得系统更加灵活和易于扩展。