一、工厂方法模式的诞生
在读这篇文章之前,我先推荐大家读《设计模式之简单工厂模式(Simple Factory Pattern)》这篇文档。工厂方法模式是针对简单工厂模式中违反开闭原则的不足,而提出的改进方案。在简单工厂模式中,每增加一个新产品,就要修改工厂类的调度方法, 针对这个问题,工厂方法模式提出,工厂类提取成抽象的类或接口,定义调度规范。每新增一种产品,不仅要提*品的实现类,还要提供调度这种产品的工厂类。这样,用户在用工厂方法模式时,需要哪个产品,就调用哪个产品的工厂类,即可获得。就不用只通过一个工厂类传参,去获得不同产品了。简单工厂模式改变的是产品参数,工厂方法模式改变的是工厂类型。
二、工厂方法模式的定义
工厂方法模式,又称工厂模式、多态工厂模式和虚拟构造器模式,通过定义工厂父类负责定义创建对象的公共接口,而子类则负责生成具体的对象。它的意义是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。
通过第一部分的描述,这些官方的定义概念,是不是很好理解了呢?
二、工厂方法模式的角色
由上面的描述,我们可以得到工厂方法中,设计到的角色:
- 产品规范:和简单工厂模式的产品规范作用一样,定义规范;
- 产品实现:和简单工厂模式的产品实现一样,实现产品规范的方法;
- 抽象工厂:工厂方法模式新加的角色,用来制定调度产品的规范,供具体工厂类遵循;
- 工厂实现类:实现抽象工厂的规范,来调度不同的产品;不同的产品有不同的工厂实现来调度;
- 产品使用者:也就是业务程序员,工厂方法模式的使用,主要是选择不同的工厂实现类,来获取不同的产品;
三、工厂方法模式的实现
首先,还以家具为例,先定义产品规范,如下:
/**
* 产品规范
*/
public interface Furiniture {
public void createFurniture();
}
然后,定义产品实现,如下:
/**
* 产品实现类
*/
public class Table implements Furiniture {
@Override
public void createFurniture() {
System.out.println("生产桌子");
}
}
/**
* 产品实现类
*/
public class Chair implements Furiniture {
@Override
public void createFurniture() {
System.out.println("生产椅子");
}
}
下面,我们要定义工厂,在这里,我们工厂也定义工厂抽象规范,让其他具体工厂类进行实现,如下:
/**
* 工厂抽象类,定义调度产品的规范
*/
public abstract class FurnitureFactory {
public abstract void getFurniture();
}
然后定义工厂的实现类,因为现在有Table和Chair两个产品,所以需要两个工厂实现类,如下:
/**
* 桌子产品的工厂类
*/
public class TableFactory extends FurnitureFactory {
@Override
public void getFurniture() {
new Table().createFurniture();
}
}
/**
* 椅子工厂的工厂类
*/
public class ChairFactory extends FurnitureFactory {
@Override
public void getFurniture() {
new Chair().createFurniture();
}
}
调用者使用工厂方法模式,获得产品:
public class FactoryMethodUser {
public static void main(String[] args) {
//对于使用者而言,要选择具体的产品工厂类,进行产品的获得
FurnitureFactory factory=new TableFactory();
factory.getFurniture();
}
}
当我们想增加产品时,如增加Sofa产品,需要增加产品实现类和产品工厂实现类。
四、工厂方法模式的优缺点
优点:
1.面向接口编程;
2.符合了开闭原则;
缺点:
添加新产品时,除了增加新产品类外,还要提供与之对应的具体工厂类,系统类的个数将成对增加,在一定程度上增加了系统的复杂度。
五、与简单工厂模式的区别
工厂方法模式与简单工厂模式的区别主要是两方面:
1.对于产品提供方来说,不光要提供新的产品实现,还要提*品的调度工厂实现类,需要提供两套规范的东西;
2.对于使用工厂模式的人来说,获得产品,不再是通过一个工厂,通过传入不同的产品参数来获得产品,而是直接选择相应的工厂实现类来获得产品即可。即简单工厂是一个工厂输出不同产品,工厂方法模式是不同工厂输出不同产品。
思考:工厂方法模式中,抽象工厂类,定义了统一规范,那产品是否有必要再定统一接口规范呢?
有必要。在定义抽象工厂的规范时,我们是要返回统一产品的。如何统一产品呢?就是定义产品规范。如果抽象工厂里不统一产品输出,那调用者调用起来,也就会产生五花八门的产品,这样明显是不合理的。
六、工厂方法模式在JDK中的应用
我们以Collection集合为例,说明工厂方法模式的应用。
Collection中用到的工厂方法模式,其实是工厂方法模式的变种,不是标准的工厂方法模式。我们来分析一下。
首先,找产品规范,是JDK定义的迭代器接口Iterable, 也就是实现产品,要继承Iterable接口,实现iterator()方法。核心代码如下图:
工厂抽象类:Collection接口,该抽象工厂定义了生产集合的规范。我们使用的ArrayList,HashSet,就是抽象工厂的实现类。
我们看Collection的源码可以发现,其继承了Iterable接口。也就是说,抽象工厂中,不光有工厂的规范,还有产品的规范。也就是说,在Collection中,将工厂规范和产品规范合二为一了。
那么我们可以猜想,工厂的实现类,和产品的实现类,也合二为一了。那么进一步推想,工厂类其实就是产品实现类了,那我们还用写调度方法吗,肯定不需要。我们只需要调用产品的方法,即可。
这样设计,就省去了工厂类调度产品,再通过产品调用产品方法的逻辑。直接通过工厂类,调用产品的方法。
我们看ArrayList的源码,其实现思路也是按上面的分析来实现的。
首先,ArrayList中无需写调度方法了,我们直接找其产品方法,iterator()方法。
由源代码可以看出,产品规范实现返回了Itr对象。来实现接口规范。那我们在调用时,直接通过ArrayList对象的iterator()就可以使用该产品了。
Collection实现工厂方法模式比较隐蔽,不容易被人发现。