(四)详解工厂模式

一.为什么需要工厂模式

当我们在拥有了大量类的时候,一旦我们需要具体对象,就需要手动控制new出不同的对象。我们可以针对这一步骤抽象化,创建一个根据要求返回我们需要的具体对象的工厂。这样就能通过统一的方式获得不同的对象

二.不用工厂模式

假设我们拥有一个披萨店,我们需要根据客户不同的订单提供不同的披萨。首先不使用工厂模式,我们就能得到这样的代码

cpp 复制代码
#include <iostream>
#include <string>

class Pizza {

    public:

    virtual void prepare() = 0;
    virtual void bake() = 0;
    virtual void cut() = 0;
    virtual void box() = 0;
};

//不同种类的披萨
class CheesePizza: public Pizza {
    void prepare()
    {
        std::cout << "准备芝士" << std::endl;
    };

    void bake()
    {
        std::cout << "烤芝士" << std::endl;
    };

    void cut()
    {
        std::cout << "切芝士" << std::endl;
    };

    void box()
    {
        std::cout << "装盒" << std::endl;
    };
};

class GreekPizza: public Pizza {
    void prepare()
    {
        std::cout << "。。。" << std::endl;
    };

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

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

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

class PepperoniPizza: public Pizza {
    void prepare()
    {
        std::cout << "。。。" << std::endl;
    };

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

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

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

std::unique_ptr<Pizza> orderPizza(std::string type)
{
    std::unique_ptr<Pizza> pizza = nullptr;

        if (type == "cheese")
        {
            pizza = std::make_unique<CheesePizza>();
        }
        else if (type == "greek")
        {
            pizza = std::make_unique<GreekPizza>();
        }
        else if (type == "pepperoni")
        {
            pizza = std::make_unique<PepperoniPizza>();
        }

    pizza->prepare();
    pizza->bake();
    pizza->cut();
    pizza->box();

    return pizza;
};

三.使用简单工厂模式

能看到假若我们想要对具体类型的披萨进行调整是很困难的。我们将工厂引入,单独用一个函数创建对象。就可以得到这样的代码。通过统一的createPizza函数获得不同的披萨对象

cpp 复制代码
#include "Pizza.hpp"
#include <string>

class SimplePizzaFactory
{
    public:
    
    std::unique_ptr<Pizza> createPizza(const std::string& type)
    {
        std::unique_ptr<Pizza> pizza = nullptr;

        if (type == "cheese")
        {
            pizza = std::make_unique<CheesePizza>();
        }
        else if (type == "pepperoni")
        {
            pizza = std::make_unique<PepperoniPizza>();
        }
        else if (type == "clam")
        {
            pizza = std::make_unique<ClamPizza>();
        }
        else if (type == "veggie")
        {
            pizza = std::make_unique<VeggiePizza>();
        }
        return pizza;
    }

};

这样结合工厂模式就可以得到如下的披萨店的代码。

cpp 复制代码
#include "SimplePizzaFactory.hpp"
#include "Pizza.hpp"
#include <string>

class PizzaStore
{
    SimplePizzaFactory factory;

public:

    PizzaStore(SimplePizzaFactory factory)
    {
        this->factory = factory;
    }

    std::unique_ptr<Pizza> orderPizza(std::string type)
    {

        std::unique_ptr<Pizza> pizza = nullptr;

        pizza = factory.createPizza(type);

        if (pizza != nullptr)
        {
            pizza->prepare();
            pizza->bake();
            pizza->cut();
            pizza->box();
        }
        else
        {
            std::cout << "不是可提供的披萨" << std::endl;
        }
        return pizza;
    }

};

这样就可以通过字符串自动判断并生成对应的对象。也有利于我们增加新的披萨种类。但是对于更改已有披萨的设计仍然不是很方便。

四.工厂模式

我们想让每种不同的披萨对象拥有更多的弹性。我们可以将这一弹性功能下放到PizzaStore实现。也就是让子类运行时根据输入决定做什么披萨,怎么做披萨。我们需要提供多种不同类型的PizzaStore,并根据不同的方法创建不同的披萨类型。

首先提供披萨店的接口和披萨的接口

cpp 复制代码
#include "Pizza.hpp"
#include <string>

class PizzaStore
{

public:

    std::unique_ptr<Pizza> orderPizza(std::string type);
    virtual std::unique_ptr<Pizza> makePizza(std::string type) = 0;

};
cpp 复制代码
class Pizza
{
    public:
    std::string name;
    std::string dough;
    std::string sauce;
    std::vector<std::string> toppings;

    const std::string& getName() const
    {
        return this->name;
    }

    virtual void prepare() const
    {
        std::cout << "准备 " + getName() << std::endl;
        std::cout << "准备面团..." << std::endl;
        std::cout << "添加酱汁..." << std::endl;
        std::cout << "添加配料: " << std::endl;
        for (auto& topping : this->toppings)
        {
            std::cout << "  " + topping + "\n";
        }
    }
    virtual void bake() const
    {
        std::cout << "350 度 烤 25 分钟 "<< std::endl;
    }
    virtual void cut() const
    {
        std::cout << "切分披萨"<< std::endl;
    }
    virtual void box()  const
    {
        std::cout << "披萨装盒" << std::endl;
    }

    virtual ~Pizza() = default;

};

有了披萨店的接口和披萨的接口,我们就能创建出不同种类的披萨商店贩卖不同种类的披萨。

首先创建出不同类型的披萨。并将其装载在披萨店内。

cpp 复制代码
#include "Pizza.hpp"

class NYStylePepperoniPizza: public Pizza
{
public:

    NYStylePepperoniPizza()
    {
        name = "NY style pepperoni pizza";
        dough = "。。。面";
        sauce = "。。。酱";
        toppings.push_back("。。。e");
        toppings.push_back("。。。");
        toppings.push_back("。。。");
        toppings.push_back("。。。");
        toppings.push_back("。。。");
        toppings.push_back("。。。");
    }


};
cpp 复制代码
#include "Pizza.hpp"
#include "PizzaStore.hpp"


class NYPizzaStore: public PizzaStore
{
    public:

    std::unique_ptr<Pizza> makePizza(std::string type)
    {
        std::unique_ptr<Pizza> pizza = nullptr;

        if (type == "cheese")
        {
            pizza = std::make_unique<NYStyleCheesePizza>();
        }
        else if (type == "pepperoni")
        {
            pizza = std::make_unique<NYStylePepperoniPizza>();
        }
        return pizza;
    }
};

这样披萨店的工厂会拥有更好的弹性,能更利于增加新的披萨,由于每个商店都拥有一个特有的披萨类,也利于披萨类本身的更改。

披萨本身和披萨店本身是平行的关系,披萨是产品类,披萨商店是创建者

工厂模式定义:定义了一个创建对象的接口,但让子类决定实例化哪个类。

本质就是工厂接口创建出工厂实例,调用产品接口实现出产品实例。

五.工厂模式应有的对象依赖关系

依赖抽象不依赖具体类,应该让依赖倒置。高层组件的类例如(pizzastore)依赖具体实现类例如(具体pizza类)。

六.抽象工厂模式

对于上面的工厂模式的披萨而言,原料都是固定好的,不利于修改,而抽象工厂模式利于提供大量家族相关对象,就比如上面的披萨原料。我们可以通过抽象工厂来改造披萨的原料。首先获得我们新的pizza虚接口,跟工厂模式没有什么变化。

cpp 复制代码
class Pizza
{
    public:
    std::string name;
    Dough dough;
    Sauce sauce;
    std::vector<std::string> toppings;

    const std::string& getName() const
    {
        return this->name;
    }

    virtual void prepare() const            //抽象工厂的重点是这个函数
    {
        std::cout << "准备 " + getName() << std::endl;
        std::cout << "准备面团..." << std::endl;
        std::cout << "添加酱汁..." << std::endl;
        std::cout << "添加配料: " << std::endl;
        for (auto& topping : this->toppings)
        {
            std::cout << "  " + topping + "\n";
        }
    }
    virtual void bake() const
    {
        std::cout << "350 度 烤 25 分钟 "<< std::endl;
    }
    virtual void cut() const
    {
        std::cout << "切分披萨"<< std::endl;
    }
    virtual void box()  const
    {
        std::cout << "披萨装盒" << std::endl;
    }

    virtual ~Pizza() = default;

};

主要的变化是我们将用到工厂内的组件也就是工厂家族,具体的实现过程抽象工厂实现。首先看一下批量获取相关原料的工厂接口。

cpp 复制代码
class PizzaIngredientFactory
{
public:
        virtual Dough CreatDough() = 0;
        virtual Cheese CreatCheese() = 0;
                .
                .
                .
};

我们可以根据不同的披萨商店,形成不同的具体原材料工厂实例

cpp 复制代码
class NYStylePepperoniPizza: public PizzaIngredientFactory
{
    Dough createDough()
    {
        return new ThinCrustDough();
    }

    Cheese createCheese()
    {
        return new MarinaraCheese();
    }
            .
            .
            .
}

我们用一个例子来实现。我们让披萨制作阶段运行时调整,通过原材料工厂获取不同的原材料

cpp 复制代码
#include "Pizza.hpp"

class NYStylePepperoniPizza: public Pizza
{
    PizzaIngredientFactory _ingFact;
public:
    
    NYStylePepperoniPizza(PizzaIngredientFactory ingFact)        //通过工厂元素获取原料
    {
        this._ingFact = ingFact;
    }

    void prepare()    //通过原料工厂批量获取对象
    {
        dough =  _ingFact.createDough();
        sauce = _ingFact.createsauce();
        cheese = _ingFact.createcheese();
    }


};

最后我们需要更改披萨商店调用原材料工厂

cpp 复制代码
#include "Pizza.hpp"
#include "PizzaStore.hpp"


class NYPizzaStore: public PizzaStore
{
    public:

    std::unique_ptr<Pizza> makePizza(std::string type)
    {
        std::unique_ptr<Pizza> pizza = nullptr;
        
        PizzaIngredientFactory idFact = new NYPizzaIngredientFactory ();    //拿到原材料工厂

        if (type == "cheese")
        {
            pizza = NYStyleCheesePizza(idFact);    //通过不同的工厂加工生成不同的披萨
        }
        else if (type == "pepperoni")
        {
            pizza = NYStylePepperoniPizza(idFact);
        }
        return pizza;
    }
};

这样就实现了不同工厂通过抽象工厂获得批量不同的元素,生成对应的披萨。

总结:通过引进一个抽象工厂提供了为披萨家族创建家族。此工厂提供接口,我们通过这个接口获取对象。我们可以通过更换工厂获得不同的行为。

抽象工厂提供一个接口来创建相关或依赖对象的家族,而不需要提供具体类

七.对比工厂模式和抽象工厂

抽象工厂通过对象组合创建对象,通过提供一个抽象接口,规定如何生产相关产品,工厂方法通过子类创建对象。

抽象工厂用于提供大批量相关产品家族。

工厂用于从具体的实例化解耦。

抽象工厂往往会在具体实例化使用工厂模式

抽象工厂缺点:添加新产品意味着改变接口。

总结:

抽象工厂提供一个接口,用于创建相关依赖对象的家族,而不必指定它们的具体类。依靠对象组合实现,子类创建在工厂接口暴露的方法中实现。抽象工厂的意图是创建相关对象家族,不必依赖具体类

工厂方法,定义一个创建对象的接口,但让子类决定哪个类实例化,工厂方法让一个类延迟实例化到子类。工厂方法依赖继承,对象创建被委托给子类,子类实现工厂方法来创建对象。工厂方法的意图是允许类延迟实例化

相关推荐
一点媛艺35 分钟前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风39 分钟前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生2 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功2 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
闲晨2 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程2 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
UestcXiye3 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
Chrikk3 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*3 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue3 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang