设计模式-装饰器模式

基础概念介绍

代码实现

基础模式
基础模式实现一

如果不使用装饰器模式,而是采用最普遍的类继承和组合的方式,我们需要直接在每个具体类中添加新的功能。这可能导致类的层次结构变得复杂,难以维护,因为每个具体类都需要预先知道所有可能的组合方式。
下面是一个简化的示例,演示了使用类继承和组合的方式来实现相同的功能:

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 接口,通过装饰器模式,我们可以在运行时动态地为咖啡添加不同的功能,而无需修改原有代码。
通过对比简化例子和这个更复杂的例子,我们可以看到使用装饰器模式的优势,特别是在处理复杂组合的情况下。它使得系统更加灵活,易于扩展,同时遵循开闭原则。

使用装饰器模式的详细解释
  1. 基础咖啡类SimpleCoffee:
    • SimpleCoffee 是具体组件,实现了 Coffee 接口。
    • getDescription 返回 “Simple Coffee”,cost 返回基础咖啡的价格。
  2. 装饰器抽象类 CoffeeDecorator:
    • CoffeeDecorator 是装饰器的抽象基类,也实现了 Coffee 接口。
    • 拥有一个指向 Coffee 对象的指针,通过构造函数接受被装饰的咖啡对象。
  3. 具体装饰器类 MilkDecorator 和 SugarDecorator:
    • 继承自 CoffeeDecorator,实现了 getDescription 和 cost 函数,以添加额外的功能。
    • MilkDecorator 添加了牛奶,SugarDecorator 添加了糖。
  4. 主函数调用:
    • 创建基础咖啡对象 SimpleCoffee,然后通过装饰器依次添加牛奶和糖。
    • 输出咖啡的描述和成本。
  5. 新增功能的装饰器 TemperatureDecorator 和 FoamDecorator:
    • TemperatureDecorator 添加了温度的装饰,FoamDecorator 添加了奶泡的装饰。
    • 这些装饰器可以动态地为咖啡对象添加新的功能,而不影响原有类的结构。
  6. 新的具体咖啡类 Espresso:
    • Espresso 是一个新的具体咖啡类,实现了 Coffee 接口。
    • 输出描述为 “Espresso”,成本为 2.0。
  7. 主函数调用中新增复杂组合的咖啡:
    • 创建一个复杂的咖啡,包括糖、牛奶、温度装饰、奶泡装饰等。
    • 输出咖啡的描述和成本。
不使用装饰器模式的对比:
  1. 基础咖啡类 SimpleCoffeeSimple:
    • SimpleCoffeeSimple 只是一个简单的咖啡类,没有考虑添加额外功能的可能性。
  2. 每个组合的咖啡类:
    • CoffeeWithMilkSimple、CoffeeWithMilkAndSugarSimple 等都是通过直接继承和组合来实现的。
    • 每个新的组合都需要创建一个新的类,导致类的层次结构不断扩展。
  3. 温度和奶泡的功能:
    • 如果要添加温度或奶泡的功能,可能需要修改每个具体类,导致类的修改。
  4. 可扩展性和灵活性:
    • 不使用装饰器模式,类的层次结构可能变得复杂,且不容易扩展。
    • 添加新的功能可能需要修改现有类,违反了开闭原则。
  5. 代码重复:
    • 针对每个具体咖啡,可能需要重复实现相似的功能,导致代码冗余。

通过比较可以看到,使用装饰器模式的优势在于系统的灵活性和可扩展性。在不使用装饰器模式的情况下,可能会导致类层次结构的不断扩展,代码变得难以维护和扩展。而装饰器模式允许在运行时动态地添加新功能,而无需修改原有类的结构,使得系统更加灵活和易于扩展。