理解设计模式之简单工厂、工厂方法、抽象工厂

工厂方法、抽象工厂都属于创建型模式,是用来创建对象的。
简单工厂,也称参数化工厂方法,是工厂方法的一种。
今天就来聊聊这些工厂相关的设计模式。

工厂类的意义

client直接创建对象Target,无形之中client和Target就形成了强耦合。解除这种耦合是设计模式的课题之一:如何优雅的替代外部new()。如果用一个中间类把Target的创建封装起来,这个中间类取名为工厂Factory,那么client是否就和Target解耦了呢?
答案是肯定的。
有的同学可能会问,client和Target解耦了但是client和Factory、Factory和Target却又耦合了,这种解耦有什么意义?
答案是有意义的。
因为这里面包含了一个变化和不变的因素,一般来说,Factory相对于Target的可变性要小的多,通过Factory可以把Target的变化封装起来。显然,client依赖于一个稳定的Factory比依赖于容易变化的Target要好的多,对于系统的稳定性和灵活性更佳。
有的同学可能会问,如果Factory可变性依然很大那怎么办?虽然这个问题有点钻牛角尖,若真是如此,就要考虑再来一个FactoryFactory了。
工厂类的意义就在于此。

但是还有一个和Factory的可变性没有关系的问题,Factory和Target的耦合怎么办?两点说明:

  1. 相对于client,Factory和Target当属一个系统,属于“内部”问题,它们之间的耦合,比client和Target的耦合要好的多;
  2. 让Factory尽量依赖Target的抽象来降低它们之间的耦合。

简单工厂

在工厂类中,通过参数创建同类型的对象

理解了上面工厂类的意义之后,简单工厂就真的再简单不过了。

首先,抽象化一下产品还是有必要的。

1
2
3
4
5
6
public interface IProduct {
}
public class ProductA implements IProduct {
}
public class ProductB implements IProduct {
}

写一个工厂类,根据参数来判断是创建ProductA还是ProductB:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Factory {
public static final int TYPE_A = 1;
public static final int TYPE_B = 2;
public IProduct create(int type) {
if (type == TYPE_A) {
return new ProductA();
} else if (type == TYPE_B) {
return new ProductB();
}
return null;
}
}

好了,client无须直接new具体的ProductA或者ProductB,而能通过Factory得到它们,当然,需要传一下参数的。
这就是简单工厂,UML示意图如下:
简单工厂UML示意图
这里可以进一步解耦client和Factory的关系,那就是static化Factory或create方法,这就是静态工厂。静态工厂让使用Factory变得简单,但也让Factory变的不那么面向对象了(不能再继承扩展),所以这是一个简单和扩展的选择,实际场景自然有它们存在的需求,应酌情使用。

简单工厂模式的代码基本上是随处可见,这样的代码应该经常写吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MainPagerAdapter extends FragmentPagerAdapter {
@Override
public Fragment getItem(int i) {
if (i == 0) {
return new HomeFragment();
} else if (i == 1) {
return new SettingFragment();
}
return null;
}
// ... ...
}

工厂方法(Factory Method)

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

简单工厂的Factory里包括一个if-else语句,这种结构意味着,如果增加或删除新的产品必须修改Factory类,这不符合开闭原则的思想,应付变化的能力有待加强。
如何改造呢?
不能再要这个if-else了,在前面已经分析了工厂类的意义了,所以分别为ProductA和ProductB包装一个FactoryA和FactoryB来负责创建它们。
既然出现了多个Factory,我们有必要抽象一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface IFactory {
IProduct create();
}
public class FactoryA implements IFactory {
@Override
public IProduct create() {
return new ProductA();
}
}
public class FactoryB implements IFactory {
@Override
public IProduct create() {
return new ProductB();
}
}

好了,现在要增加新的产品,只需要增加ProductC和FactoryC而不会修改现在的框架代码。
当然,client现在要创建ProductA,不再是传参数了,直接创建FactoryA;创建ProductB的话,直接创建FactoryB好了。
有的同学就要问了,是不创建Product了,但是全变成创建Factory了,感觉一样啊?
答案是,不一样的。请回看“工厂类的意义”。
工厂方法的UML示意图如下:
简单工厂UML示意图

PS:这里client和具体Factory的耦合,可以根据具体场景,用反射、注解等技术手段再解耦,本文的重点不在于此,不做讨论。

抽象工厂(Abstract Factory)

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

如果ProuctA和ProductB并不是同类产品,只是语义相关的或者有一定的依赖关系,那么工厂模式的Factory的一个接口就不够了,的这时候。
比如苹果既生产Phone也生产Watch,都是基于ios系统,都是苹果负责。还有小米,三星也是如此…
有人就说了,那定义两个IFactory吧,一个IFactoryA,一个IFactoryB,但是请注意这些产品对象的“关系”,把它们放到同一个IFactory才是最合适的。所以说,如果说这些工厂模式提供了天生的松耦合的思想,抽象工厂则还体现高内聚的思想。
既然搞的这么复杂(其实并不复杂),那我们还是抽象一下吧:

1
2
3
4
5
6
7
8
public interface IFactory {
IProductA createA();
IProductB createB();
}
public interface IProductA {
}
public interface IProductB {
}

具体产品类:

1
2
3
4
5
6
7
8
public class ProductA1 implements IProductA {
}
public class ProductA2 implements IProductA {
}
public class ProductB1 implements IProductB {
}
public class ProductB2 implements IProductB {
}

具体工厂来负责创建这些具体产品类,并按门别类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Factory1 implements IFactory {
@Override
public IProductA createA() {
return new ProductA1();
}
@Override
public IProductB createB() {
return new ProductB1();
}
}
public class Factory2 implements IFactory {
@Override
public IProductA createA() {
return new ProductA2();
}
@Override
public IProductB createB() {
return new ProductB2();
}
}

从上面的代码,也看的出高内聚带来的问题,增加新的产品需要修改所有的Factory,这很要命,这是它的“缺点”。
但是这真的是缺点吗?
是缺点,但不是这个模式的重点,每个模式都是有自己的侧重点的,抽象工厂的侧重点是能创建有关联或者一系列的对象的工厂抽象出来,这些对象是不能拆开的,如果行的通的话,甚至可以如此组合:

1
2
3
4
5
6
7
8
9
10
11
12
public class Factory3 implements IFactory {
@Override
public IProductA createA() {
// 小米收购了苹果,不生产自己的手表,改为生产苹果的手表
return new ProductA2();
}
@Override
public IProductB createB() {
return new ProductB1();
}
}

当你改变关注点时事情就会发生变化。抽象工厂的UML示意图如下:
抽象工厂UML示意图

从这个图中,这里再强调一下,抽象工厂和工厂方法的模型是有本质区别的:

工厂方法(包括简单工厂)的ProductA和ProductB是同源的,都是继承自IProduct。
但是抽象工厂不是,ProductA1和ProductB1有各自的接口,它们是关联类,这是我们在实际场景“套用”它们的时候要特别注意的。

思考:
我偏偏就要完美的工厂模式,既要工厂方法的完美扩展,又要抽象工厂的产品关联,那怎么办呢?

当工厂遇见单例

说到静态工厂的时候,也许大家就意识到,client对Factory的使用如果每次都需要new一下Factory,在绝大多数场景下没必要,甚至是一种资源的浪费。
结合单例,就变得水到渠成了,我们把FactoryA改造成SingletonFactoryA:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SingletonFactoryA implements IFactory {
// 单例实现
private volatile static SingletonFactoryA instance;
private SingletonFactoryA() {
}
public static SingletonFactoryA getInstance() {
if (instance == null) {
synchronized (SingletonFactoryA.class) {
if (instance == null) {
instance = new SingletonFactoryA();
}
}
}
return instance;
}
@Override
public IProduct create() {
return new ProductA();
}
}

单例模式不是本文重点,在此不展开讨论。

小结

如果说简单工厂是抽象了1:N的工厂-产品关系,工厂方法则是抽象了N:N的工厂-产品关系,抽象工厂则是抽象了N:M的工厂-产品关系。
计算机领域有一句名言:“All problems in computer science can be solved by another level of indirection”,计算机科学领域的所有问题都可以通过增加一个间接中间层来解决,真是一语中的,这正是工厂类的精要所在。