cpp学习设计模式:工厂方法模式
在学习工厂方法模式之前,先回忆前面学的简单工厂模式;
简单工厂模式就是将对象的创建和逻辑的判断都交给了一个工厂类去做,这样做的优点是客户端不需要知道具体产品类的类名和具体产品的生产过程,达到分离的目的;
但是简单工厂模式的缺点也是不容忽略的,那就是工厂类的责任太过重大,而且如果产品类比较多的话,判断逻辑也会比较复杂,使系统的逻辑变得难理解;
上面是对简单工厂模式的回忆;那么和我们现在要学习的工厂方法模式有什么关系呢?
工厂方法模式是属于GoF23种模式中的一种,它是基于简单工厂模式的设计模式,不过不同的是,工厂方法模式继承了简单工厂方法的优点,同时也没有简单工厂方法的缺点,即造成工厂类的复杂逻辑;而且如果要添加新的产品类的话,就不得不去修改工厂类的逻辑,违背了开闭原则,这是 我们不希望看到的;
那么,工厂方法模式到底是怎么做到这点的呢,下面我们先来看看工厂方法模式的定义;
工厂方法模式:工厂方法模式又称为工厂模式,也叫虚拟构造器模式或者多态工厂模式(当然,这里是Java的多态,和C++还是有区别的),它属于创建型模式;在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生产具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟该实例化哪一个具体产品类;
画张图,可以更直观的看出工厂方法模式和简单工厂模式的区别;
如果可以忽略画的图比较丑的话,我觉得还是能明显的看出,工厂方法和简单工厂方法的最大不同之处:将责任重大的工厂类,即简单工厂模式只有一个工厂类,解放出来,进而变为多个工厂子类,将具体产品的生产交给具体的工厂,而如果要添加新的产品类的话,只需要再添加一个新的工厂来服务即可,不再修改工厂逻辑;
看到这,肯定有很多人很疑惑了,那么具体的工厂子类是如何选择的?即如果没有了简单工厂模式那样的复杂逻辑判断,该如何决定具体产品生产的工厂呢? (我昨晚也琢磨了好一会);
这个就是工厂模式屏蔽简单工厂模式的巧妙之处,说巧妙吧其实也不巧妙,嘿嘿;
举个例子:我现在有一个车间(抽象工厂基类),有四条生产线(工厂子类),分别生产四种品牌的电视(产品对象类),但是此时动力机器只有一台,即每次只能运作一台生产线,而决定运作那一条生产线的要素在于,启动机器的钥匙,钥匙有四把,也分别对应四种品牌的电视,需要生产哪一种品牌的电视只需要插入对应的钥匙即可,机器就会运转对应的生产线; 那么这个钥匙就很关键; 说简单那点,其实就是提前将生产线确定了,并且一次只能运转一条生产线,如果现在运转的是Hair, 你想要LOL电视的产品的话,就需要拔出Hair的钥匙,插入LOL的钥匙,将选择权抽象了出来,而不是再通过简单的逻辑判断;
其它的地方都很好理解,烦的就是这个钥匙,到底是什么,在《设计模式》这本书上,采用的是Java 的两个机制XML文档的配置文件,在这个文档中写入子工厂的类名;接着利用DOM机制操纵XML文档,读取到这个字符串,产生出这个类的具体对象; 这就是Java的反射机制;
简而言之,有一个文档专门存放需要的子工厂的类名(就是钥匙),而有专门的方法可以拿到这个类名,进而产生一个实例; 这样就不需要进行什么逻辑判断,是Hair还是LoL之类的; 但是每次只能运作一个产品对象的生产,修改的是这个XML文档,而不会去修改工厂类,这符合了开闭原则;
由于Java比较渣,又想不到用C++合适的方法来模式这个对XML文档操作的模拟,真的是有点无奈;
这是Java实现反射机制的关键代码:即通过类名来生成实例对象并返回
//code = Java
Class c = Class.forName("String");
Object obj = c.newInstance();
return obj;
下面用C++ 伪代码来简单实现:
#include<iostream>
using namespace std;
class TV
{
public:
//纯虚函数,抽象基类,公共方法;
virtual void play() = 0;
virtual ~TV() //注意将基类的析构函数设为虚函数
{
cout<<"~TV"<<endl;
}
};
class Hair:public TV
{
public:
void play()
{
cout<<"Hair tv working!"<<endl;
}
~Hair()
{
cout<<"~Hair"<<endl;
}
};
class TCL:public TV
{
public:
void play()
{
cout<<"TCL tv working!"<<endl;
}
~TCL()
{
cout<<"~TCL"<<endl;
}
};
class BaseFactory
{
public:
//纯虚函数,抽象类;
virtual TV* product() = 0;
virtual ~BaseFactory() //注意将基类的析构函数设为虚函数
{
cout<<"~BaseFactory"<<endl;
}
};
class SonHairFactory:public BaseFactory
{
public:
TV* product()
{
//Hair子工厂返回Hair产品对象;
//这里其实最好使用智能指针来维护这个对象,因为C++没有Java的垃圾回收机制
//容易造成内存泄漏;
cout<<"Hair tv being producted!"<<endl;
return new Hair();
}
~SonHairFactory()
{
cout<<"~SonHairFactory"<<endl;
}
};
class SonTCLFactory:BaseFactory
{
public:
TV* product()
{
cout<<"TCL tv being producted!"<<endl;
return new TCL();
}
~SonTCLFactory()
{
cout<<"~SonTCLFactory"<<endl;
}
};
//我们直接以main函数来模拟客户端;
int main()
{
//注意,C++千万不要写成TV tv,会被笑的,抽象基类不可以不实例化;
TV* tv;
BaseFactory* factory;
//factory = ("Java反射机制得到的子工厂队形");
//C++的话现在只能将子工厂类名暴露出来了
factory = new SonHairFactory();
tv = factory->product(); //生产的对象传给tv指针,Java的话直接是对象;
tv->play();
//释放空间; Java不需要,有垃圾回收机制;
delete(tv);
delete(factory);
system("pause");
return 0;
}
测试结果
实现的过程大概描述出来,就是没有Java的反射机制,在后面如果找到合适的方法,我再来替换;不过重点是理解工厂方法模式的思想;
工厂方法模式的优点
- 基本具有简单工厂模式的优点,不再赘述;
- 添加新产品时不需要修改工厂类,符合开闭原则,就是弥补了简单工厂模式的缺点;
工厂方法模式的缺点
- 添加新产品时需要添加对应的产品类和子工厂类比较麻烦,还有就是产品比较多的时候,类泛滥的问题;
- 使用反射机制,需要进行抽象层,就是增加了系统的实现难度;
- 单一的执行,每次只能单一的执行一种产品的生产;生产下一种产品需要对XML文档进行修改,我们这里C++的话就是每次需要替换子工厂类名;
小结
因为语言机制的不够了解,导致模拟的不够完全,同时对工厂方法模式理解的不够通彻,希望在下一节学习抽象工厂模式的时候,会对工厂方法模式有一个更完整的理解!