C++创建型设计模式之 工厂模式

工厂模式简介

首先,工厂模式也是一种创建型设计模式,用于对复杂对象的创建。它与上篇讲到的构造器模式不同之处在于,它可用于对生成对象时限制条件的验证,也可用于处理多个复杂类型的集中构造,并且对象构造的条件是否必备及已生成对象之间约束条件是否满足等,都可以在一个工厂方法中进行约束限制。白话一点就是,你想创建这个对象,那你给的条件满不满足要求,即使创建出来了,和之前创建的对象有没有冲突等检查,如果有任何不满足的地方,都应停止构造或返回nullptr指针。

单个类型的工厂方法创建

拿一个香蕉类来举例子,如果不加任何限制,那么直接默认构造就行了

class banana
{
public:
    banana()=default;
}

ps:无参构造函数或析构函数=default 相当于在后面写了个{},默认构造。

如果有条件限制,糖尿病人不能吃香蕉(仅限讨论技术,并不一定真不能吃哈),是否为糖尿病人参数很容易传进来banana(bool diabetes){ … },但diabets = true时,我们该如何停止构造呢?因为在构造函数里又不能return false; 就算在构造函数里抛异常来阻止构造也是很不明智的,所以最好的办法是另起一个专用于构造banana的类,另写一个CreateBanana类,有了这个类,再将banana构造函数受保护,实现就变的简单和安全很多了。

class Banana
{
public:
    static Banana* createBanana(bool diabetes = false)
    {
        return diabetes ? nullptr : new Banana();
    }
private:
    Banana() = default;
};

如果不想让用户再继承Banana类而导致子类可能存在安全问题,那么也可以将构造函数私有(private)。
这种强制用户只能构造特定条件的对象主方法,就叫着工厂方法

面对更复杂的情况时,仅凭createBanana一个接口就显得太鸡肋了,可以嵌套个类,来专门验证构造的前置条件。假如,糖尿病人正常服药的话,也是可以吃香蕉的。

class Banana
{
    //other members
private:
    Banana() = default;
    class BananaFactory
    {
        BananaFactory() = default;
    public:
        Banana* getBanana(bool dabetes, bool bTreat)
        {
            return dabetes && !bTreat ? nullptr : new Banana;
        }
    };
public:
    static BananaFactory factory;
};

我们把BananaFactory 类放在Banana的私有部分,并且Banana类和BananaFactory类自己构造函数都被私有化,所以完全限制了外部的构造和使用,用户只能通过我们开放的static BananaFactory factory来获取想要的对象了,我们可以用Banana* banana = Banana::factory.getBanana(true, true)来获取。但接口变更为类后,对复杂的限制条件我们就更方便处理了。这种将工厂类嵌入类型的作法叫嵌套工厂

对于复杂的条件限制,其实我们还有一个相对独立的方法,就是将回调函数当成参数传入getBanana接口中

Banana* getBanana(bool dabetes, bool bTreat, std::function<bool(void)> func=nullptr)
  {
     if (func && !func()) return nullptr;
     return dabetes && !bTreat? nullptr : new Banana;
  }

那么func 回调函数的定义就允许用户更自由的在外部定义了,这对使用时用户有特殊需求情况很有效。并且,此处回调函数参数只是void的情况,也可以按需求进行设置。
用接口直接调用回调函数也是可以的,是否需要嵌套类可按需设计。

多个类型的创建工厂

先举个根据水果颜色创建水果的例子,当然吃完说出它的味道这事是使用对象范畴,不是工厂模式的业务。

#include <iostream>

class Fruit
{
public:
    Fruit() = default;
    virtual void taste() = 0;
};
class Apple : public Fruit
{
public:
    Apple() = default;
    void taste() override
    {
        std::cout << "Apple taste sweet!" << std::endl;
    }
};
class Banana : public Fruit
{
    //other members
public:
    Banana() = default;
    void taste() override
    {
        std::cout << "Banana taste delicately!" << std::endl;
    }
};
class Oriange : public Fruit
{
public:
    Oriange() = default;
    void taste() override
    {
        std::cout << "oriange taste acid!" << std::endl;
    }
};
class fruitFactory
{
public:
    enum class fruitEnum
    {
        appleEnu = 0,
        bananaEnu,
        oriangeEnu
    };
    fruitFactory() = default;
    Fruit* getFruit(fruitEnum fruit)
    {
        switch (fruit)
        {
        case fruitEnum::appleEnu:
            return new Apple;
        case fruitEnum::bananaEnu:
            return new Banana;
        case fruitEnum::oriangeEnu:
            return new Oriange;
        default:
            return nullptr;
        }
    }
};
int main()
{
    fruitFactory().getFruit(fruitFactory::fruitEnum::appleEnu)->taste();
    //其它水果类似
    system("pause");
}

这种获取对象的特点是:

  • 类型定义可以在其它任意地方,而获取这些类型的对象都在同一个工厂里进行。

  • 获取对象的接口也只有一个(可以拓展为多个,但每个接口创建的类型一定不能有交叉),凭借给定的条件获取不同对象。

  • 每个对象在创建时,默认构造或带简单的构造参数(不然,各类型都需要不同的参数时,接口所需传的参就没法定义了)。

由于此类型的工厂比较简单,适用于少量类型的快速组织代码,如果类型增加需继续修改原工厂内的代码来拓展,传到非限定内的参数就会构造失败了,所以这种模式一般叫简单工厂

因为对原代码直接进行修改的行为违反了软件工程的开闭原则(对拓展开放,对修改关闭),所以我们最好还是将fruitFactory的接口定义为虚接口,这种后期拓展时便可不断通过继承重写来应对实际情况中的变化了。

virtual Fruit* getFruit(fruitEnum fruit)
    {
        switch (fruit)
        {
        case fruitEnum::appleEnu:
            return new Apple;
        case fruitEnum::bananaEnu:
            return new Banana;
        case fruitEnum::oriangeEnu:
            return new Oriange;
        default:
            return nullptr;
        }
    }

这种将虚函数重写的工厂一般称之为抽象工厂

总结拓展

  1. 工厂模式一般不直接返回对象,因为返回对象时肯定会发生复制行为,那么在工厂内部是无法对已创建的对象进行相关行为的后期管理的。
  2. 一般谁创建谁负责销毁,像以上例子里这样给别人一个裸指针,很可能造成使用者直接丢弃而没有被delete情况,所以返回智能指针要比裸指针安全很多。
  3. 很多时候在工厂内部需要对已创建的对象进行监视,可以在工厂内存储所有已生成的对象,再定义接口获取,如果存储的为shared_ptr还可以查看外部对该对象引用的次数。不过为了防止对象不能正常的被析构,工厂内存储weak_ptr来完成这一工作更合适。
  4. 实际中,不仅工厂相关的构造形式需要不断升级,外部水果种类也会不断被丰富,那么抽象工厂在重写时,将可以发挥更大作用。
  5. 工厂生成的对象一般为基类类型,如果直接调用非虚的接口就只能进行基类中。进入子类接口可能需要使用dynamic_pointer_cast进行转换。
  6. 一个工厂可不是只能有一个获取对象的接口,可以定义多个接口,一个负责创建水果,另一个负责创建鞋子,或其它更多。

工厂模式内容比较杂乱,使用时方法也是灵活多变的。具体的开发过程中可根据实际情况进行设计。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值