Factory Method 工厂方法
意图
定义一个用于创建对象的接口,让子类决定实例化哪个类。Factory Method使得一个类的实例化延迟到其子类。
Factory Method又称作Virtual Constructor(虚构造器)。
在软件系统中,经常面临着创建对象的工作,而需求的变化常常导致所需要创建的对象的具体类型发生变化。
Factory Method使得可以绕过常规的对象创建方法(new关键字),将封装机制引入到对象的创建工作中,从而避免了客户程序与“具体对象创建工作”产生紧耦合。
不过要注意,不可能做到完全绕过new进行对象创建,我们想要做的,是将创建对象的职责向上转移,从而是子程序具有更高的灵活性。
代码案例
假如,现在有若干类,每一个子类代表一种动物:
class Animal{
virtual void shout() = 0;
}
class Rabbit: public Anlmal{
void shout(){
cout << "ZI";}
}
class Cat: public Anlmal{
void shout(){
cout << "MIAO";}
}
class Dog: public Anlmal{
void shout(){
cout << "WANG";}
}
我们在函数中这样调用它:
void func()
{
Animal* an = new Rabbit();
an->shout();
}
如上所示,我们创建了一个兔子对象,并调用了它的Shout方法。
乍一看,其实这段代码并没有什么不妥的地方,但它实际上违反了一个重要的设计原则:依赖倒置原则,详细来说,就是**基类对象的创建依赖于子类的存在。**也就是说,在这段代码中,class Rabbit必须存在来使得代码顺利编译通过。
听起来不是什么大事?举个例子,在团队合作的项目中,这就要求编写函数的人必须拥有一个子类(大概率不是他本人负责编写的类)的副本,扩大来说,团队中的每一个人都必须拥有这个基类每个子类的副本。在子类被更新时,每个人都必须更新自己手上的类文件。
或许如果你学过编译原理的话,用静态链接和动态共享的类比会更好理解:这种方法白白占用了更多的内存。更重要的是,它为项目的维护带来了不便。下面我会对这一点做更详细的解释。
创建类设计模式的产生,就是为了将对象的创建作为一个单独的模块维护,从而降低系统维护难度而产生,现在,我们来看看FactoryMethod怎样解决这个问题。
解决方案:
首先创建这样一个类:
class AnimalFactory
{
virtual Animal* CreateAnimal() = 0;
}
接着,为每一个存在的Animal子类编写对应的工厂子类。
class RabbitFactory: AnimalFactory{
Animal* CreateAnimal(){
return new Rabbit();
};
}
class CatFactory: AnimalFactory{
Animal* CreateAnimal(){
return new Cat();
};
}
class DogFactory: AnimalFactory{
Animal* CreateAnimal(){
return new Dog();
};
}
这样,我们就可以用工厂来代替New进行对象创建了。为了满足依赖倒置原则的要求,我们用这种方式来指定工厂的种类:
class solution
{
AnimalFactory* af;
solution(AnimalFactory* p): af(p){
}
void func()
{
Animal* an = af.CreateAnimal();
an.shout();
}
}
这样一来,solution类与Animal某一子类的紧耦合关系就被解除了,只需要创建solution对象时指定它对应的工厂即可。没错,这又是我们在设计模式学习中常见的技巧:编译时多态转运行时多态。
看上去,我们只是把对象创建延迟到了上一级,有点多此一举的意味。实际上,这对程序的封装性是很大的改善。编写solution类的程序猿,完全不需要知道子类是什么,团队又可以愉快地一起工作了。
解释
事实上,创建对象型设计模式的原理都很类似(它们包括FactoryMethod、AbstractFactory, 而Builder不同点多一些)。都是将对象创建的过程作为一个单独模块封装起来,从而降低程序内部的耦合性。
FactoryMethod主要遵循了依赖倒置原则,它使得编写基类相关的方法时不需要依赖特定的某个子类。
关于这一点,我在这里用这样一个例子来说明:你在考驾照时,不关心将来自己具体要开哪种车,只关心开的是手动挡、自动挡、小轿车还是大货这种问题。不使用FactoryMethod的话,就好像你在学驾照的时候,就要清楚将来要开桑塔纳还是别克,这就是一种依赖倒置。
再以上文为例:既然func函数与子类是什么没有关系,那么就不应当要求存在某一特定的子类使得func函数编译通过,甚至不存在任何一种子类时,它也应当正常编译(相当于纸上谈兵)。
总结
设计模式 | Factory Method (工厂方法) |
---|---|
稳定点: | 工厂产品的参数(每种子类创建时需要的参数类型必须相同) |
变化点: | 工厂的产品种类(子类类型) |
效果: | 编写操作基类的函数时不需要对其具体子类有任何的了解 |
特点: | 将对象创建作为单独的模块封装起来 |
类图:
2021.2.9 转载请标明出处