在网上的大多数资料里面,将工厂模式分为:简单工厂、工厂方法、抽象工厂,他们之间的联系(以电子厂为例)如下:
那么该文章首先讲解工厂模式是什么,为什么需要工厂模式,最后再对三种模式进行示例。该文章是23招做项目的第一篇文章,参考文献和其他设计模式的讲解看专栏,现在2020.8.06正在逐步更新。
1. 工厂模式是什么
工厂模式的主要应用是在工具包和框架。工厂模式要解决的问题是:解决接口没法new的问题,希望能够创建一个对象,但创建过程比较复杂,希望对外隐藏这些细节。实质是new的一个替代品,任何你需要创建对象,而又不想指明它的具体class的时候都可以用。
在平时编程中,构建对象最常用的方法就是new一个对象,这种new对象方法属于一种硬编码,在比较小的项目里面是没有什么毛病的,但是在大的项目里面会出现一个问题。每new一个对象,相当于调用者多知道一个类,增加了类与类之间的联系,不利于程序的松耦合,容易搞成屎山。其实构建过程可以被封装起来,工厂模式便是封装对象的设计模式。
打个比方,直接new一个对象相当于我们做肯德基的新奥尔良汉堡的时候,我们不仅要知道鸡排是调味道的\还要知道怎么样揉面包才使得面包比较松软,但是有了新奥尔良汉堡工厂就不一样了,管他什么三七二十一,有现成的面包和调好味道的鸡扒,将鸡扒的烤好塞进去就完事了。
以做汉堡为例:
//C++,这是汉堡工厂
//作者:阿伟加油鸭,首发于优快云,禁止转载,原文网址:https://blog.youkuaiyun.com/qq_45877524/article/details/107778702
class HumbegerFactory
{
public:
Humbeger * createHumbeger(string type){
switch(type){
case "鳕鱼汉堡": return new CodBurger();
case "牛肉汉堡": return new BeefBurger();
}
};
//C++,这是调用者
//作者:阿伟加油鸭,首发于优快云,禁止转载,原文网址:https://blog.youkuaiyun.com/qq_45877524/article/details/107778702
class User{
private:
void eat(){
HumbegerFactory humbegerFactory = new HumbegerFactory();
Humbeger codBurger = humbegerFactory.createHumbeger("鳕鱼汉堡");
Humbeger beefBurger = humbegerFactory.createHumbeger("牛肉汉堡");
codBurger.eat();
beefBurger.eat();
}
};
事实上,将构建过程封装的好处不仅可以降低耦合,如果产品的构造十分复杂,使用工厂模式可以减少代码重复。比如,生产一个新奥尔良汉堡需要面包、鸡扒时,可以将汉堡工厂修改如下:
//C++,这是汉堡工厂的2.0版本
//作者:阿伟加油鸭,首发于优快云,禁止转载,原文网址:https://blog.youkuaiyun.com/qq_45877524/article/details/107778702
class HumbegerFactory
{
public:
Humbeger * createHumbeger(string type){
switch(type){
case "鳕鱼汉堡":
//修改部分
Breed codBurgerBreed = new CodBurgerBreed();
return new CodBurger();
case "牛肉汉堡": return new BeefBurger();
}
};
不太懂耦合概念的小伙伴,可以看一下这个杨博的回答讲的非常好:程序设计经常提到的解耦,到底是要解除什么之间的耦合?
而调用者的代码则完全不需要改变,而且调用者不需要在每次做汉堡的时候,自己去揉面包、调鸡扒的味道,搞了半天才弄出个汉堡。面包揉的方法再复杂,也是工厂的事情,与我无关,这就是封装的好处。而且,万一有一天广东的汉堡厂要去提供鸡扒给湖南的肯德基,那就直接在工厂里面加点辣就🆗了,湖南的门店就不用特意加拉。
其实在不知不觉间已经完成了简单工厂的代码,在开始三个模式的论述,我想要解决一个问题:为什么工厂方法不用构造函数?
2. 既然有了构造函数,我为什么要折腾这么多事情,我直接一个东西全部构造了不就完事了吗
注意该段观点来自于: 工厂模式(factory Method)的本质是什么?为什么引入工厂模式?中大佬:高宽宽的回答
既然有了构造函数,我为什么要折腾这么多事情,我直接一个东西全部构造了不就完事了吗。为了解答这个问题,首先解释一下构造函数是干嘛地。
对于C语言而言,创建对象资源是这样的(该程序中指定了p的内存大小):
//c内存分配示例
//作者:阿伟加油鸭,首发于优快云,禁止转载,原文网址:https://blog.youkuaiyun.com/qq_45877524/article/details/107778702
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char * a=NULL;
a=(char *)malloc(sizeof(char));
if(a == NULL){
printf("false");
return -1;
}
printf("%d\n",*a);
free(a);
printf("%d\n",*a);
return 0;
}
在面向对象的程序语言出现之前,创建一个对象是很繁琐的东西,要先分配内存,做类型转化,还要初始化,然后才能使用,一不小心还会忘了分配内存的释放(这一点我相信大部分大一学生在学C语言,经历过谭老头的坑)。
因此,程序语言面向对象之后,构造函数被发明出来,将分配内存与初始化合并在一起,例如:
//c++构造函数示例,这个图算法看之前
//作者:阿伟加油鸭,首发于优快云,禁止转载,原文网址:https://blog.youkuaiyun.com/qq_45877524/article/details/107778702
class AdjMatrixUndirNetwork //顶点为字符型
{
public:
AdjMatrixUndirNetwork(int vertexMaxNum = DEFAULT_SIZE, int infinite = DEFAULT_INFINITY);
AdjMatrixUndirNetwork(char* vexs, int vertexNum, int vertexMaxNum = DEFAULT_SIZE, int infinite = DEFAULT_INFINITY);
~AdjMatrixUndirNetwork();
//……
private:
char* vertexes;
……
};
AdjMatrixUndirNetwork::AdjMatrixUndirNetwork(int vertexMaxNum, int infinite)
{
vexMaxNum = vertexMaxNum;
……
}
AdjMatrixUndirNetwork::AdjMatrixUndirNetwork(char* vexs, int vertexNum, int vertexMaxNum, int infinite)
{
……
}
这样做确实没什么毛病,你像做个图算法地示例,这样做绝对是最优解,因为这些项目非常简单,也就是只有几个参数。但是,整个构造函数所能够完成的工作从一个复杂的项目(几千万行代码的那种)还是太过初级了。因此复杂的创建逻辑是需要写代码来控制,当你有任何复杂的创建对象过程时,你都需要写一个某种createXXX的函数来实现,其实哪怕不是对象,而是任何资源。
//类似这样子
//作者:阿伟加油鸭,首发于优快云,禁止转载,原文网址:https://blog.youkuaiyun.com/qq_45877524/article/details/107778702
class User{
private:
void eat(){
HumbegerFactory humbegerFactory = new HumbegerFactory();
Humbeger codBurger = humbegerFactory.createHumbeger("鳕鱼汉堡");
Humbeger beefBurger = humbegerFactory.createHumbeger("牛肉汉堡");
codBurger.eat();
beefBurger.eat();
}
};
等一下,究竟什么是比较复杂的创建逻辑呢?例如
1.知道怎么创建一个对象,但是无法把控创建的时机。你需要把“如何创建”的代码塞给“负责什么时候创建”的代码。后者在适当的时机,就回调创建的函数
2."构造函数里不要抛出异常"但问题是,业务要求必须在这里抛一个异常怎么办?,createHumbger解决这个问题。
3. 工厂模式——一场面向过程与面向对象之间的较量
不具体叙述看下面链接。
来源于leetcode的文章:一场「面向对象」与 「面向过程」 的较量
3.简单工厂
简单工厂就是一台机器专门负责制造螺丝钉,之前构建过的简单工厂模式的代码了:
//C++,这是汉堡工厂的2.0版本
//作者:阿伟加油鸭,首发于优快云,禁止转载,原文网址:https://blog.youkuaiyun.com/qq_45877524/article/details/107778702
class HumbegerFactory
{
public:
Humbeger * createHumbeger(string type){
switch(type){
case "鳕鱼汉堡":
//修改部分
Breed codBurgerBreed = new CodBurgerBreed();
return new CodBurger();
case "牛肉汉堡": return new BeefBurger();
}
};
简单工厂作为工厂方法的一个特例,就是让一个工厂类承担起构建所有对象的职责,调用者需要一些什么,我就给他一些什么。这里不打算画出UML(不太标准,因为标准的在线画里面要钱),一笔带过,再在工厂方法里面进行详细诉述。
弊端在于:
- 如果需要生产的产品过多,此模式会导致工厂类边的超级大,一点出现问题,他会有许多引起修改的原因,违背了单一职责原则。如果要求螺丝的大小都不一样,一直改,有一台机器忙不过来
- 当要生产新的产品时,必须在工厂类中添加新的分支。本身用工厂模式就是不想理里面到底是什么东西,烦。违背了开闭原则。
4.工厂方法
为了解决简单工厂模式的两个弊端,工厂方法规定每个产品都有一个专属于自己的机器,工厂方法更多的像一个流水线。意图是定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到子类,以上面做汉堡为例子:
//C++,工厂方法模式的示例,这是工厂部分
//作者:阿伟加油鸭,首发于优快云,禁止转载,原文网址:https://blog.youkuaiyun.com/qq_45877524/article/details/107778702
class CodBurger{
public:
Humburger * create(){
return new Cod();
}
};
class BeefBurger{
public:
Humburger * create(){
return new Beef();
}
};
//这是调用者部分
class User{
private:
void eat(){
CodBurger codBurger = new CodBurger();
Humburger cod = codBurger.create();
BeefBurger beefBurger = new BeefBurger();
Humburger beef = beefBurger.create();
codBurger.eat();
beefBurger.eat();
}
};
不是,我这样和直接new有什么区别,有多少种汉堡就需要知道几个工厂类,耦合度完全没有降低,甚至增加了代码量。请注意,工厂模式的运用场景是——希望解决的是比较复杂的创建过程,这一点在析构函数那里解释了。这里的例子比较简单,可能看不出来,但是当汉堡比较复杂,例如:
//鳕鱼汉堡比较复杂的示例
//作者:阿伟加油鸭,首发于优快云,禁止转载,原文网址:https://blog.youkuaiyun.com/qq_45877524/article/details/107778702
class CodBurger{
public:
Humburger * create(){
PickledCucumbers pickledCucumbers = new PickledCucumbers(); //酸黄瓜
Salad salad = new Salad(); //沙拉酱
Sesame sesame = new Sesame(); //芝麻
Breed breed = new Breed(); //面包
return new Cod(pickledCucumbers,salad,sesame,breed)
}
};
其中沙拉酱之类的基本上所有的汉堡都有的东西,我完全可以直接用一个东西工厂类把这个打包起来,最后在根据各自不一样的的东西,比如鳕鱼条进行组装汉堡,而调用者所要作仅仅是改个夹的东西,其他都不用理,这不就是减少了代码量和耦合了吗。
工厂方法模式的优点:‘
- 用工厂方法在一个类的内部创建对象通常比直接创建对象灵活。Factory Method给子类一个钩子以提供对象的扩展版本。
- 当需要生产新的东西时,无需改变原有的工厂,只需要增加新的工厂即可。保持了面向对象的可扩展性,符合开闭原则。
单词 | 意思 |
---|---|
Product | 定义工厂方法所创建的对象的接口 |
ConcreteProduct | 实现Product |
Creator | 声明工厂方法,该方法返回一个Product类型的对象 |
ConcreteCreator | 重新定义工厂方法以返回一个ConcreteProduct |
C++代码模板示例,其他语言可以看这个链接:最下面就是
//工厂
class Creator {
public:
virtual Product* CreateProduct() = 0;
};
template<class TheProduct>
class StandardCreator :public Creator {
public:
virtual Product* CreateProduct();
};
//用户
template <class TheProduct>
Product* StandardCreator<TheProduct>::CreateProduct() {
return new TheProduct;
}
class MyProduct::public Product {
public:
MyProduct();
//……
};
StandardCreator<MyProduct> myCreator;
5.抽象工厂
抽象工厂作为工厂方法的2.0版本,更进一步把整个工厂作为一个接口(不理会一条流水线的生产结果,直接着眼于整个系列,在C++里面采用了继承的方法,看下面示例就知道了。
名称 | 解释 |
---|---|
AbstractFactory | 声明一个创建抽象产品对象的操作接口 |
ConcreteFactory | 实现创建具体产品对象的操作 |
AbstractProduct | 为一类产品对象声明一个接口 |
ConcreteProduct | 定义一个将被相应的具体工厂创建的对象 |
Client | 仅仅使用由AbstractFactory和AbstractProduct类声明的接口 |
C++示例如下,其他语言在该链接的最后面
#include <iostream.h>
class Shape {
public:
Shape() {
id_ = total_++;
}
virtual void draw() = 0;
protected:
int id_;
static int total_;
};
int Shape::total_ = 0;
class Circle : public Shape {
public:
void draw() {
cout << "circle " << id_ << ": draw" << endl;
}
};
class Square : public Shape {
public:
void draw() {
cout << "square " << id_ << ": draw" << endl;
}
};
class Ellipse : public Shape {
public:
void draw() {
cout << "ellipse " << id_ << ": draw" << endl;
}
};
class Rectangle : public Shape {
public:
void draw() {
cout << "rectangle " << id_ << ": draw" << endl;
}
};
class Factory {
public:
virtual Shape* createCurvedInstance() = 0;
virtual Shape* createStraightInstance() = 0;
};
class SimpleShapeFactory : public Factory {
public:
Shape* createCurvedInstance() {
return new Circle;
}
Shape* createStraightInstance() {
return new Square;
}
};
class RobustShapeFactory : public Factory {
public:
Shape* createCurvedInstance() {
return new Ellipse;
}
Shape* createStraightInstance() {
return new Rectangle;
}
};
int main() {
#ifdef SIMPLE
Factory* factory = new SimpleShapeFactory;
#elif ROBUST
Factory* factory = new RobustShapeFactory;
#endif
Shape* shapes[3];
shapes[0] = factory->createCurvedInstance(); // shapes[0] = new Ellipse;
shapes[1] = factory->createStraightInstance(); // shapes[1] = new Rectangle;
shapes[2] = factory->createCurvedInstance(); // shapes[2] = new Ellipse;
for (int i=0; i < 3; i++) {
shapes[i]->draw();
}
}