创建型设计模式-抽象工厂模式

模式介绍

前段时间介绍了工厂方法模式
本次介绍的模式非常容易和工厂方法模式混淆,并且复杂度也有一定的提升,叫做抽象工厂模式
其实在现实生活中,工厂都会生产某一样具体的产品,不存在说抽象的工厂。
那么该模式的含义到底是什么呢?
抽象工厂模式的出现最早是为了解决不同操作系统下的图形化处理,这就好比iOS中的Button、Android中的Button、WindowPhone中的Button一样,虽然都是Button,但系统的不同,会导致Button也存在差异。
这就引出了抽象工厂模式的定义:
为创建出一组相互依赖的对象提供一个封装接口。
定义中有几个关键字,需要注意:

  • 一组:这是和工厂方法模式的重要区别所在,抽象工厂模式用于生产多个产品,而不是一个。如果生产一个产品,请使用工厂方法模式建造者模式
  • 相互依赖:生产出一组产品之后,这组产品一定是相互依赖的,注意是一定。就好比上面的例子中,Android系统能使用iOS系统的Button吗?答案是否定的,因为iOS的Button依赖于iOS系统。

解释完名词,想必大家对抽象工厂模式的使用场景就有了一些认知:
当产品很多,并且有特定的关联可以进行抽象,就可以使用抽象工厂模式

模式构成

抽象工厂模式的主要角色还是4个:

  • AbstractFactory:抽象工厂,包含一组产品的生产抽象,每一个方法都应对应一个产品
  • ConcreteFactory:抽象工厂实现,应包含生产一组产品的具体实现。
  • AbstractProduct:一组产品中的一个产品抽象。
  • ConcreteProduct:具体的产品实现。

模式示例

现在我们开始实现在上述介绍中提到的例子。
我们先来将思路捋一捋:
对应关系

  1. 我们产品包含:操作系统、Button。
  2. 工厂应该生产一组产品,即生产操作系统和Button。
  3. 生产出来的一组产品应相互依赖,并且和另一组产品相互独立。

接下来,我们就按照思路来实现抽象工厂模式
首先我们来初始化抽象产品AbstractProduct
操作系统System:

1
2
3
4
5
6
public interface SystemAbsProduct {

//获取系统型号
String getSystem();

}

Button:

1
2
3
4
5
6
public interface ButtonAbsProduct {

//按钮响应
String getButtonName(SystemAbsProduct system);

}

我们可以发现,Button与操作系统存在依赖关系,这种依赖关系的体现由开发人员自己来控制,我这里只是个简单实例。
两个产品的抽象已经有了,我们先不急着去实现它们。
接下来我们来定义抽象工厂AbstractFactory,它应该有两个方法,分别返回系统和Button两个产品:

1
2
3
4
5
6
7
8
public interface AbsFactory {

//创建操作系统
SystemAbsProduct createSystem();

//创建Button
ButtonAbsProduct createButton();
}

到这里应该都没有什么问题,抽象工厂、抽象产品都已经创建完成了。
接下来我们想要分别构建出WindowPhone系统、Android系统、iOS系统下的Button。
我们应该能想到,必须要先要有具体的产品,才能用具体工厂来生产。
所以接下来,我们要先创建具体的产品:
操作系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public interface SystemAbsProduct {

//获取系统型号
String getSystem();

public class WindowPhone implements SystemAbsProduct{

@Override
public String getSystem() {
return App.context.getString(R.string.window_phone);
}
}
public class iOS implements SystemAbsProduct{

@Override
public String getSystem() {
return App.context.getString(R.string.ios);
}
}

public class Android implements SystemAbsProduct{

@Override
public String getSystem() {
return App.context.getString(R.string.android);
}
}
}

Button:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public interface ButtonAbsProduct {

//按钮响应
String getButtonName(SystemAbsProduct system);

public class WindowPhoneButton implements ButtonAbsProduct {

@Override
public String getButtonName(SystemAbsProduct system) {
if (system.getSystem().equals(App.context.getString(R.string.window_phone))) {
return "点击了WindowPhone系统的Button";
}
return App.context.getString(R.string.system_error);
}
}

public class IOSButton implements ButtonAbsProduct {

@Override
public String getButtonName(SystemAbsProduct system) {
if (system.getSystem().equals(App.context.getString(R.string.ios))) {
return "点击了iOS系统的Button";
}
return App.context.getString(R.string.system_error);
}
}

public class AndroidButton implements ButtonAbsProduct {

@Override
public String getButtonName(SystemAbsProduct system) {
if (system.getSystem().equals(App.context.getString(R.string.android))) {
return "点击了Android系统的Button";
}
return App.context.getString(R.string.system_error);
}
}
}

因为类的数量庞大,所以我选择使用内部类的形式来实现ConcreteProduct角色。
我们可以发现,在Button角色中,我们进行了简单的判断,当操作系统不匹配时,会提示用户,这就类似于运行崩溃的效果。
接下来,我们来创建最后一个角色:ConcreteFactory。
它应该有多个,每一个负责创建一组具体的产品,也就是说有多少组产品,就应该有多少个具体工厂:
WindowPhoneFactory:

1
2
3
4
5
6
7
8
9
10
11
public class WindowPhoneFactory implements AbsFactory {
@Override
public SystemAbsProduct createSystem() {
return new SystemAbsProduct.WindowPhone();
}

@Override
public ButtonAbsProduct createButton() {
return new ButtonAbsProduct.WindowPhoneButton();
}
}

IOSFactory:

1
2
3
4
5
6
7
8
9
10
11
public class IOSFactory implements AbsFactory {
@Override
public SystemAbsProduct createSystem() {
return new SystemAbsProduct.IOS();
}

@Override
public ButtonAbsProduct createButton() {
return new ButtonAbsProduct.IOSButton();
}
}

AndroidFactory:

1
2
3
4
5
6
7
8
9
10
11
public class AndroidFactory implements AbsFactory {
@Override
public SystemAbsProduct createSystem() {
return new SystemAbsProduct.Android();
}

@Override
public ButtonAbsProduct createButton() {
return new ButtonAbsProduct.AndroidButton();
}
}

三个具体工厂分别具体产生三组相互依赖的产品。
至此,简单的抽象工厂模式已经构建完成。
但是最关键的还是使用,接下来我们来写一些简单的测试代码:

1
2
3
4
5
6
7
8
//构建WindowPhone具体工厂
AbsFactory iOSFactory = new IOSFactory();
//构建操作系统
SystemAbsProduct iOSSystem = iOSFactory.createSystem();
//构建按钮
ButtonAbsProduct iOSButton = iOSFactory.createButton();
//调用依赖
Toast.makeText(this, iOSButton.getButtonName(iOSSystem), Toast.LENGTH_SHORT).show();

创建iOS系统工厂,接着创建iOS系统和Button,最后调用Button的点击,会发现成功提示出调用成功。
如果我们使用错误的依赖:

1
2
3
4
5
6
7
8
AbsFactory androidFactory1 = new AndroidFactory();
SystemAbsProduct androidSystem1 = androidFactory1.createSystem();
ButtonAbsProduct androidButton1 = androidFactory1.createButton();
AbsFactory iOSFactory1 = new IOSFactory();
SystemAbsProduct iOSSystem1 = iOSFactory1.createSystem();
ButtonAbsProduct iOSButton1 = iOSFactory1.createButton();
//使用Android的Button时,传入iOS操作系统
Toast.makeText(this, androidButton1.getButtonName(iOSSystem1), Toast.LENGTH_SHORT).show();

上述演示代码,会提示系统不匹配。

总结

抽象工厂模式的最大优势就是分离了接口与实现,客户端使用工厂来创建所需对象,实现面向产品的接口编程,使其在切换产品类时更加灵活、容易。
当然缺点也非常明显,一是类文件的增加,二是不太容易扩展产品类,因为每添加一个新产品,都要修改工厂,随之所有工厂实现都要修改。
抽象工厂模式工厂方法模式的区别也很明显:
抽象工厂模式用来生产一组相互依赖的产品。
工厂方法模式用来批量生产一种产品。
抽象工厂模式的相关代码已经上传至GitHub,需要的同学可以下载参考。

感谢

抽象工厂模式和工厂模式的区别-caoglish的回答

《Android源码设计模式解析与实战》 何红辉、关爱民 著