文章目录

正文

` 最近学习了一套工厂模式的代码模板,不仅代码清晰简洁,而且具有极高的扩展性。

` 首先编写一段最基本的izza工厂的基本代码,然后再慢慢改写成最终形式的代码模板。

#include <iostream>
#include <vector>
#include <string>

// 基本的披萨类
class Pizza {
public:
    std::string name;
    Pizza(const std::string& name) : name(name)    void prepare() {
        std::cout << "Preparing " << name << std::endl;
    }

    void bake() {
        std::cout << "Baking " << name << std::endl;
    }

    void cut() {
        std::cout << "Cutting " << name << std::endl;
    }

    void box() {
        std::cout << "Boxing " << name << std::endl;
    }
};

int main() {
    // 客户想要的披萨列表
    std::vector<std::string> pizzaOrders = {"Margherita", "Pepperoni"};

    // 遍历每个订单并准备相应的披萨
    for (const auto& order : pizzaOrders) {
        // 直接创建披萨对象
        Pizza pizza(order);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
    }

    return 0;
}

在这个例子中,Pizza 类非常简单,它有一个构造器来接受披萨的名称,并且有几个方法来模拟制作披萨的过程:prepare、bake、cut 和 box。然后在 main 函数中,我们循环遍历披萨列表,为每个订单创建一个新的 Pizza 对象,并调用其方法来模拟披萨的制作过程。这种方法适用于代码规模较小,对象创建逻辑不复杂,且不需要频繁修改或扩展的场景。

若要引入工厂模式的理念来改写购买披萨的代码,我们会创建一个披萨工厂类,

#include <iostream>
#include <memory>
#include <string>

// 披萨基类
class Pizza {
public:
    virtual void prepare() {
        std::cout << "Preparing base pizza" << std::endl;
    }

    virtual void bake() {
        std::cout << "Baking base pizza" << std::endl;
    }

    virtual void cut() {
        std::cout << "Cutting base pizza" << std::endl;
    }

    virtual void box() {
        std::cout << "Boxing base pizza" << std::endl;
    }

    virtual ~Pizza()};

// 具体的披萨类型
class MargheritaPizza : public Pizza {
public:
    void prepare() override {
        std::cout << "Preparing Margherita Pizza" << std::endl;
    }
    // 省略其他方法的具体实现...
};

class PepperoniPizza : public Pizza {
public:
    void prepare() override {
        std::cout << "Preparing Pepperoni Pizza" << std::endl;
    }
    // 省略其他方法的具体实现...
};

// 披萨工厂
class PizzaFactory {
public:
    std::unique_ptr<Pizza> orderPizza(const std::string& type) {
        std::unique_ptr<Pizza> pizza = createPizza(type);
        if (pizza) {
            pizza->prepare();
            pizza->bake();
            pizza->cut();
            pizza->box();
        }
        return pizza;
    }

private:
    std::unique_ptr<Pizza> createPizza(const std::string& type) {
        if (type == "Margherita") {
            return std::make_unique<MargheritaPizza>();
        } else if (type == "Pepperoni") {
            return std::make_unique<PepperoniPizza>();
        } else {
            std::cout << "Invalid pizza type." << std::endl;
            return nullptr;
        }
    }
};

int main() {
    PizzaFactory factory;

    // 客户点了一份玛格丽塔披萨和一份意大利辣香肠披萨
    auto margherita = factory.orderPizza("Margherita");
    auto pepperoni = factory.orderPizza("Pepperoni");

    // 如果还有其他订单,可以继续从工厂订购...

    return 0;
}

在这个重写的版本中,我们有:
Pizza 基类,定义了所有披萨所共有的方法。MargheritaPizza 和 PepperoniPizza 类,它们是 Pizza 的具体子类,覆盖了基类中的方法以提供特定行为。PizzaFactory 类,包含 orderPizza 方法,该方法接受一个披萨类型的字符串,用于创建特定类型的披萨,并通过调用 createPizza 私有方法实现具体的创建逻辑。createPizza 方法根据类型字符串来决定实例化哪个披萨子类。main 函数中,我们创建了一个 PizzaFactory 实例,并使用它来处理披萨订单。使用工厂模式的优点是,添加新的披萨类型时,只需要修改工厂类,而不需要修改main函数。
`
但是引入工厂模式的代码,仍然显得十分冗长,代码可读性很差,可以通过引入一些高级特性和宏定义来解决这些问题,我们将使用一个映射表来根据披萨类型的字符串标识符来创建相应的披萨对象

#include <iostream>
#include <map>
#include <string>
#include <functional>
#include <memory>
#include <vector>

// Pizza 基类
class Pizza {
public:
    using ptr = std::shared_ptr<Pizza>;
    virtual ~Pizza()    virtual void prepare() = 0;
    virtual void bake() = 0;
    virtual void cut() = 0;
    virtual void box() = 0;
};

// 具体的披萨类型
class MargheritaPizza : public Pizza {
public:
    void prepare() override { std::cout << "Preparing Margherita Pizza" << std::endl; }
    void bake() override { std::cout << "Baking Margherita Pizza" << std::endl; }
    void cut() override { std::cout << "Cutting Margherita Pizza" << std::endl; }
    void box() override { std::cout << "Boxing Margherita Pizza" << std::endl; }
};

class PepperoniPizza : public Pizza {
public:
    void prepare() override { std::cout << "Preparing Pepperoni Pizza" << std::endl; }
    void bake() override { std::cout << "Baking Pepperoni Pizza" << std::endl; }
    void cut() override { std::cout << "Cutting Pepperoni Pizza" << std::endl; }
    void box() override { std::cout << "Boxing Pepperoni Pizza" << std::endl; }
};

// 披萨工厂
class PizzaFactory {
public:
    static Pizza::ptr createPizza(const std::string& type) {
        const auto it = pizzaCreators.find(type);
        if (it != pizzaCreators.end()) {
            return it->second();
        }
        std::cerr << "Unknown pizza type: " << type << std::endl;
        return nullptr;
    }

private:
    static std::map<std::string, std::function<Pizza::ptr()>> pizzaCreators;
};

std::map<std::string, std::function<Pizza::ptr()>> PizzaFactory::pizzaCreators = {
    {"Margherita", []() { return std::make_shared<MargheritaPizza>(); }},
    {"Pepperoni", []() { return std::make_shared<PepperoniPizza>(); }},
    // Add more pizza types and their corresponding lambdas here
};

// 客户端代码
int main() {
    std::vector<std::string> pizzaTypes = {"Margherita", "Pepperoni"};

    for (const auto& type : pizzaTypes) {
        Pizza::ptr pizza = PizzaFactory::createPizza(type);
        if (pizza) {
            pizza->prepare();
            pizza->bake();
            pizza->cut();
            pizza->box();
        }
    }

    return 0;
}

我们用 PizzaFactory 类中的 createPizza 方法来根据传入的披萨类型字符串创建相应的披萨对象。用 std::map 来存储披萨类型字符串和对应的创建函数的关系,这些创建函数是以 lambda 表达式的形式实现的。如果需要添加新类型的披萨就变得很容易,只需添加一个新的类并更新 pizzaCreators 映射表即可。这样的代码结构清晰、易于扩展,适合用于需要创建多种类型对象的情况。
接下来我们可以定义一个宏来自动化 Pizza 类型和其创建函数的映射过程。这样,我们只需要在映射表中添加一行,即可将新的 Pizza 类型及其创建函数添加到映射表中,


// 定义宏XX,用于映射披萨类型字符串到披萨创建函数
#define XX(pizzaType, PizzaClass) \
    { #pizzaType, []() -> Pizza::ptr { return std::make_shared<PizzaClass>(); } }
// 使用宏XX初始化映射表
std::map<std::string, std::function<Pizza::ptr()>> PizzaFactory::pizzaCreators = {
    XX(Margherita, MargheritaPizza),
    XX(Pepperoni, PepperoniPizza),
    // 更多披萨类型可以用同样方式添加
};
// 取消宏定义,防止在其他地方意外使用
#undef XX

这里,我们定义了一个宏 XX,它接受两个参数:披萨类型的字符串标识符和相应的 Pizza 类。宏将这两个参数扩展成 std::map 的键值对,其中键是披萨类型的字符串,值是一个lambda表达式,用来创建对应 Pizza 类的实例。

在映射表 pizzaCreators 的初始化中,我们使用 XX 宏定义了两种披萨类型。这种使用宏定义的方法使得添加新的披萨类型变得更为简洁:只需要在映射表中添加一行 XX 宏调用即可。

这种方法的优点是减少了重复代码并且增加了可维护性,尤其是当有许多披萨类型需要添加到映射表时。不过,宏可能会使得代码的错误调试变得困难,因为它们在编译前就被替换了,可能导致编译器给出的错误信息难以理解。

更多推荐