0%

工厂模式详解

简单工厂

工厂模式是JAVA中常见的一种设计模式,有一定C++多态概念之下最适合优先理解这篇文章C++类多态 | Coding中。。。 (jiuriri.com)。所以这一篇会尽量从我最擅长的C++视角之下来一步步解析工厂各类模式。

  • 简单工厂
  • 工厂方法
  • 抽象工厂
  • 模板工厂
  • 单例工厂

 

工厂

在面向对象设计模式中,我们经常把代码实现需求形象得描述为工厂生成产品的过程,如果将产生产品的一整个流程代码称为“生产线”,又由于产品的种类有很多种,每一种产品之间又会有细微的区别,为每一件具有细微差别的产品都分配一条“生产线”是不理想的,就好比开一家早餐店,那么采购不同原料只需要交给一位师傅就可以,烹饪不同早点也只需要一位师傅就行,不必要为了单一产品给他单独分配不同的人来干,这样考虑的话,也算是对工厂模式具有了一定概念——也就是在编码过程中逐渐向开闭原则等一系列面向对象设计原则靠拢。

 

简单工厂

在学习多态时的第一个例子,其实就是简单工厂的具体实现,也可以使用虚函数说实现了最基础的多态的程序就可以视为简单工厂,所以简单工厂不存在任何现实意义,只能当作教学切入点。

class Compute
{
public:
    virtual ~Compute(){};
    virtual void solve()=0; 
};

class Dell : public Compute
{
public:
void solve(){
cout<<"Dell compute"<<endl;
}
};

class Lenovo : public Compute
{
public:
void solve(){
cout<<"Lenovo compute"<<endl;
}
};

enum COMPUTE_TYPE
{
    Dell,
    Lenovo
};

class Factory
{
public:
    Compute* produce(COMPUTE_TYPE type){
        switch(type){
            case Dell: return new Dell;break;
            case Lenovo: return new Lenovo;break;
            default: cout<<"fault"<<endl;
        }
    }
};
int main(){
    Factory fac;
    Compute* com=fac.produce(Dell);
    if(com!=NULL){
        com->solve();
        delete com;
        com=NULL:
    }
    return 0;
}

简单工厂由工厂类、抽象产品类和具体产品类构成,他将产品实现的方法抽象化,交付给工厂进行制造的时候只需要给予产品名称就能开始制造。缺点是扩展性很差,增加产品类型的时候需要更改大量源代码。

简单工厂最明显的缺陷就在于当抽象产品类通过用户接口获取产品的一切数据并交付给工厂之后,工厂对于产品的生产线是十分固执的,也就是说工厂内没有明确的业务流程分组,当需要进行业务调整的时候(增加新产品)需要对整个团队都进行调整(直接修改工厂类),这直接违背了开闭原则中的“对更改封闭”。

 

工厂方法

工厂方法模式也就是“经典工厂模式”,拿简单工厂与之对比就更能凸显工厂模式的优势了。

虽然工厂模式本身还是有不少缺陷,但是我们可以先从最严重的问题开始,我们先整改工厂业务流程,让他至少能“对更改封闭,对扩展开放”。

class Factory{
public:
    virtual Compute* produce() = 0;
    virtual ~Factory(){};
};

class DellFactory : public Factory{
public:
Compute* produce(){
return new Dell();
}
};

class LenovoFactory : public Factory{
public:
Compute* produce(){
return new Lenovo();
}
}

int main(){
    Factory* pDellFac = new DellFactory();//先建厂
    Compute* pDell = pDellFac->produce();//建立生产线
    pDell->solve();
    delete pDellFac;
    delete pDell;
    //之后再想生产其他产品,直接添加新产品类和工厂类,再像这样建立生产线即可
    return 0;
}

经典工厂模式最大的特点就是像这样,符合开闭原则,是最精髓的工厂模式。

再者,就是对这个经典工厂模式进行不断优化。

 

抽象工厂

经典工厂使用起来并不是这么顺手,因为生产新类型产品的时候需要重新建立新工厂,还要重新建立新生产线因为工厂理应能生成各种各样的产品,也就是说有没有可能将生产线放入同一个工厂当中呢。

//略去产品类,这里的产品类新增一个父类,除了原有的compute加一个keyboard,并分别衍生出不同品牌的产品
//直接开始建立工厂
class Factory{
public:
    virtual Compute* ProduceCom() = 0;
    virtual Keyboard* ProduceKey() = 0;
    virtual ~Factory(){}
};

class DellFactory : public Factory{
public:
Compute* ProduceCom(){
return new DellCompute();
}
Keyboard* ProduceKey(){
return new DellKeyboard();
}
};

class LenovoFactory : public Factory{
public:
Compute* ProduceCom(){
return new LenovoCompute();
}
Keyboard* ProduceKey(){
return new LenovoKeyboard();
}
};

int main(){
    Factory* pDellFac = new DellFactory();
    Compute* pDellCom = pDellFac->ProduceCom();
    Keyboard* pDellKey = pDellFac->ProduceKey();
    pDellCom->solve();
    pDellKey->solve();
    delete pDellFac;
    delete pDellCom;
    delete pDellKey;
    //这就是戴尔电脑和戴尔键盘的完整生产线,戴尔公司真不考虑考虑我吗?
    return 0;
}

这里最大的特点是Dell和Lenovo分开建厂了!代价是他们必须为自己的所有产品负责,因为在这种模式中同品牌的不同产品已不再遵循开闭原则。

在实际生产过程中需要具体设计“抽象程度”,比如电脑肯定要搭配键盘,所以电脑生产线和键盘生产线放在一起看起来是如此理所应当。

抽象工厂模式和经典工厂模式其实差不太多,可以说经典工厂和简单工厂就是抽象化的两种极端,而抽象工厂就是他们折中的地位。

  • 简单工厂——每次建立新产品生产线都需要修改工厂类
  • 经典工厂——每次建立新产品生产线都需要增加工厂类
  • 抽象工厂——建立新产品生产线视抽象程度而定

但是还是不够,你见过哪个工厂因为某个产品直接单独建厂或者直接大动干戈换人吗?哪怕是台积电生产芯片也有不同型号的吧

 

模板工厂

前三种工厂模式能大致描述出整个工厂模式的思想,真正地将“生产产品”这个动作用面向对象的思维描述出来了,也就是从代码层面上真正有了“设计图”、“工厂”、“流水线”的概念。

现在让我们回忆并总结一下前三种模式的工厂特点,因为我们需要从中发掘缺陷并进行优化。

  • 设计图重要性反而不重要,因为任何产品都需要至少一张蓝图,设计图类无非就是一个归属权的问题,所以在此应该难以进行优化。
  • 我们更具不同品牌开设了不同工厂(抽象工厂),尽管他们生产的产品是一样的,但可惜品牌之间并不共享生产技术,于是工厂之间完全切割。
  • 想要新增一些产品要么就改工厂类,要么就新增工厂。

目前看来目的十分明确了,就是优化生产过程,因为拥有一张蓝图产出一种产品是理所应当的。所以有没有一种类似秘书助理类能帮助我们直接根据产品“参数”将生产任务发布出去呢?

template<class AbstractProduct_t>
class Factory{
public:
    virtual AbstractProduct_t* Product() = 0;
    virtual ~Factory(){}
};

template<class AbstractProduct_t,class ConcreteProduct_t>
class ConcreteFactory : public Factory<AbstractProduct_t>
{
public:
AbstractProduct_t *Product()
{
return new ConcreteProduct_t();
}
};

额实际上我还真不是很熟悉C++模板

ok现在是共产主义时代,所有工厂都共享其生产资料,所有客户都只需要提交表(含产品品牌、产品参数)给秘书姐姐模板(<class AbstractProduct_t,class ConcreteProduct_t>),让他帮忙辅助规划,就可以直接生产出你想要的产品了!

int main(){
    ConcreteFactory<Compute,DellCompute> Factory;
    Compute* pDellCompute = Factory.Product();
    //很奇怪,明明工厂类都完全优化完了,
    //但实际使用的时候要是要一个个将产品名号所对应的工厂或者方法给标识出来
    pDellCompute->solve();
    delete pDellCompute;
    pDellCompute=NULL;
    return 0;
}

 

产品注册模板+单例工厂模板类

工厂类优化完毕了,新增产品的时候不需要新增工厂类了,也不需要重写工厂类代码了。

但编写模板工厂类的时候还是会有一个很大的疑惑——那就是我们在写main函数的时候,也就是直接响应用户需求的时候,不是还得要一个个把工厂、生产线都给创建出来然后再一个个按照产品名号去调用他们,也就是说虽然秘书小姐姐帮忙统筹规矩工厂生产规范,但是当我们想要某个品牌的某产品的时候,还是要一个个去汇报签名,这实在是太繁琐了!

我们需要真正意义上的“万能工厂”,需要我们叫出产品名号后马上得到产品。

也就是说我们给工厂输入产品名称之后马上得到产品类的对象——于是乎马上能够调用产品功能。

从模板工厂开始入手,发现最终需要优化的地方在这几句:

ConcreteFactory<Compute,DellCompute> Factory;
Compute* pDellCompute = Factory.Product();
pDellCompute->solve();

先建立了某种产品的定制工厂,然后再通过该工厂进行生产。但是如果可以省去“建立工厂”这一步就好了,因为每次代码运行退出该段代码的生命周期之后工厂将会直接消失。

没有工厂当然生产不了产品,于是我们可以直接将所有产品,或者说将所有需要生产的产品的工厂提前建立,而且永久建立,这里姑且叫他“工业园”,那么是不是可以省去建立工厂这一步呢?

更为官方的说法,是将对应产品进行“注册”,定义注册类,所有真正需要生产的产品用注册类来完成,而工厂则使用单例模式定义,工厂的职责就是“进行注册”并使用注册类中的方法进行产品取出。

//基类抽象产品注册类(接口)
template<class ProductType_t>
class IProductRegistrar{
public:
    virtual ProductType_t* CreateProduct = 0;//该类的主要作用,也就是直接进行生产
protected://构造函数和析构函数子类可以调用,防止用户进行构造
    IProductRegistrar(){}
    virtual ~IProductRegistrar(){}
private://同样拷贝构造函数和运算符也需要保护起来
    IProductRegistrar(const IProductRegistrar &);
    const IProductRegistrar &operator=(const IProductRegistrar &);
};
template<class ProductType_t>//典型单例模式建立
class ProductFactory{
public:
    static ProductFactory<ProductType_t> &Instance(){
        static ProductFactory<ProductType_t> instance;
        return instance;
    }
    
void RegisterProduct(IProductRegistrar&lt;ProductType_t&gt;* registrar,std::string name){
    m_ProductRegistry[name] = registrar;
}
//产品注册
ProductType_t *GetProduct(std::string name){
    if(m_ProductRegistry.find(name)!=m_ProductRegistry.end()){
        return m_ProductRegistry[name]-&gt;CreateProduct();
    }
    std::cout&lt;&lt;&quot;No product found for&quot;&lt;&lt;name&lt;&lt;std::endl;
    return NULL;
}
//产品生产,用户对其直接进行调用

//以下单例规定

private://比如除了自己完全不让其他人构造
ProductFactory(){}
~ProductFactory(){}
ProductFactory(const ProductFactory &);
const ProductFactory &operator=(const ProductFactory &);

std::map&lt;std::string,IProductRegistrar&lt;ProductType_t&gt; *&gt; m_ProductRegistry;
//用于存储注册过的产品

};

template<class ProductType_t,class ProductImpl_t>//具体产品注册模板
class ProductRegistrar : public IProductRegistrar<ProductType_t>
{
public:
   // 构造函数,用于注册产品到工厂,只能显示调用
   explicit ProductRegistrar(std::string name)
   {
      // 通过工厂单例把产品注册到工厂
    ProductFactory<ProductType_t>::Instance().RegisterProduct(this, name);
   }

   // 创建具体产品对象指针
   ProductType_t *CreateProduct()
   {
      return new ProductImpl_t();
   }
};

抽象注册类定义了产品的生产接口,可以视为将模板工厂中的工厂基类,但原来的工厂类现在变为“管理生产”的类了,此时此刻继承抽象注册类的具体注册类也就对应了一个个的种类不同的产品。单例工厂类通过他们的统一接口来进行调用, 产品的挑选则由用户给出的产品名字来向map中进行寻找,map返回的正是相应接口。

参考:C++ 深入浅出工厂模式(进阶篇) - 知乎 (zhihu.com)