工厂方法模式和抽象工厂模式

工厂方法模式(Factory Method)

这个模式在之前看《图解设计模式》已经有部分了解,那本书并没有解释为何工厂方法模式要求一个抽象工厂的角色……这里讲的比较清楚。

简单工厂的主要一个缺点是,工厂类的职责过重,存在大量 if-else 逻辑,且如果添加新产品时必须要修改工厂类,这违反了开闭原则和单一职责原则(而且,添加子类需要修改基类,这个怎么说也感觉有点怪吧?)。而工厂方法模式则消除了这一缺点——它给所有工厂一个抽象的基类(或接口),给每个产品提供它对应的工厂。如果添加新产品,只需要定义新的工厂类和产品即可。客户端可以通过反射等手段进行调用。

从简单工厂到工厂方法

书中举了个十分容易理解的例子(不过我认为有改进的空间)——

客户需要一个日志记录器,且需要能不更改源代码(即不需要重新编译)灵活选择不同的记录器。需求分析发现,日志记录器的初始化比较复杂,可能需要初始化其他相关的类,可能需要配置工作环境等,如果都写在构造函数里会导致构造函数庞大,不利于维护。因此,使用简单工厂方法进行系统设计,其中 LoggerFactory 为工厂角色,FileLogger,DatabaseLogger 为具体产品,Logger 为抽象产品——

代码如下——

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
39
40
// Logger.java
public abstract class Logger {
public abstract void writeLog();
}

class FileLogger extends Logger{
public void writeLog() {
System.out.println("我是一个 fileLogger");
}
}
class DatabaseLogger extends Logger{
public void writeLog() {
System.out.println("我是一个 DatabaseLogger");
}
}

// LoggerFactory.java
public class LoggerFactory {
private LoggerFactory(){}
public static Logger getLogger(String type) {
Logger res = null;
if (type.equals("FileLogger")) {
res = new FileLogger();
// 做一些处理
}
else if (type.equals("DatabaseLogger")) {
res = new DatabaseLogger();
// 做另一些处理
}
return res;
}
}

// Main.java 客户端
public class Main {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger("FileLogger"); // 可以写在配置文件里
logger.writeLog();
}
}

现在,缺点来了,一旦需要增加新的 logger,必须更改工厂类,并且工厂类包含所有 logger 的初始化代码,职责过重。现在,通过使用工厂方法模式来解决这个问题。

工厂方法的要点在于,给定一个抽象工厂类,任何具体工厂作为它的子类,对每一个工厂,都有一定的产品与之适配。用户构造工厂时以抽象工厂为类型。形如下面的代码——

1
2
3
4
5
public static void main(String[] args) {
LoggerFactory loggerFactory = LoggerFactory.getFactory("Logger"); // 也可以写在配置文件里
Logger logger = loggerFactory.getLogger();
logger.writeLog();
}

这里的 getFactory 是个 static 方法,它应当根据字符串返回对应的工厂。书中的示例将这相关的代码放到外面,而我觉得这个方法其实很适合作为抽象工厂的一个静态方法。这里也可以将要生成的工厂写在配置文件里以保证开闭原则。

这个方法中可以仍旧使用 if-else 来返回工厂(这陷入了一个循环吗?并非如此,因为工厂的初始化是简单的,不需要抽象工厂关心的,它只需返回一个 new 的对象即可。但是这仍旧违反了开闭原则——添加新工厂还是要修改抽象工厂的源代码),也可以使用反射机制。

简单工厂模式可以使用反射来去掉 if-else 循环吗?不可以!每个具体产品的初始化过程是不同的,因此不同产品的初始化代码不能被抽象到一致。而工厂方法模式将产品的初始化下放到具体工厂中,其只负责具体工厂的初始化——只需要调用其构造函数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// LoggerFactory.java
public abstract class LoggerFactory {
public abstract Logger createLogger();
public static LoggerFactory getFactory(String type) {
try {
return (LoggerFactory) Class.forName(type).newInstance();
}
catch (Exception e) { // 应该进一步处理
e.printStackTrace();
return null;
}
}
}

// Main.java
public class Main {
public static void main(String[] args) {
LoggerFactory factory = LoggerFactory.getFactory("FileLoggerFactory"); // 可以写在配置文件里
Logger logger = factory.createLogger();
logger.writeLog();
}
}

要添加新的实例?没问题,创建两个新文件,比如 ImageLogger.java 和 ImageLoggerFactory.java,其中 ImageLogger 要继承 Logger 类,ImageLoggerFactory 要继承 LoggerFactory 类,然后在配置文件里修改要生成的工厂即可。这里完全不需要更改任何源代码,只需要增加文件即可!这保证了开闭原则——对扩展开放,对修改封闭。

这里也可以进行一些优化,比如重载 createLogger 方法以适应更多情况,比如可以直接在抽象工厂中定义 writeLog 方法,使客户端可以使用抽象工厂直接进行操作(这里使用了模板方法)——

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class LoggerFactory {
public abstract Logger createLogger();
public static LoggerFactory getFactory(String type) {
try {
return (LoggerFactory) Class.forName(type).newInstance();
}
catch (Exception e) { // 应该进一步处理
e.printStackTrace();
return null;
}
}
public void writeLog() {
Logger logger = this.createLogger(); // 模板方法模式——定义一个处理流程,让子类实现其中的具体步骤。
logger.writeLog();
}
// 客户端
public static void main(String[] args) {
LoggerFactory factory = LoggerFactory.getFactory("FileLoggerFactory"); // 可以写在配置文件里
factory.writeLog();
}
}

总结

工厂方法模式的优点是显而易见的,保证开闭原则,且消除了简单工厂中工厂类负担过重的问题,且用户不需知道自己究竟使用的是什么类即可完成任务(从直接使用变成间接使用……感觉这是保持开闭原则不可或缺的。..)。而它的缺点是会让系统中的类的数量成对增加,且要使用反射等手段,影响了系统的理解难度。

工厂方法模式的类图如下——

抽象工厂模式

工厂方法的缺点在于,它一个工厂只负责一类产品,因此会导致类的数量增加,增加维护难度。抽象工厂模式要求它的具体工厂并非只生成一类产品,而是一“族”产品。这对要求所有产品都符合一定规范是必要的,比如不同操作系统的各种 UI 控件,比如软件的皮肤……

一族所指的并非是相似的关系,或者父类相同的关系,如白色按钮和蓝色按钮,而是具有相互约束关系的东西,即使它们没有直接关系,比如某个操作系统下的文本框和按钮(它们都是属于这个操作系统的)。

工厂方法模式和抽象工厂模式的差别在于,工厂方法模式中每个具体工厂生产一类产品,比如白色按钮,红色按钮,蓝色按钮…抽象工厂模式中每个具体工厂生产一族产品,比如白色按钮,白色弹窗……可以说,工厂方法模式(中的每一系列工厂)针对的是一个产品等级结构(一系列继承结构),抽象工厂模式则需要面对多个产品等级结构。每一个工厂等级结构都需要负责多个不同产品等级结构中工厂的创建

显然,抽象工厂模式中,抽象工厂应当定义生成所有产品的接口,当为抽象工厂模式中这一族产品添加新成员的时候,需要修改所有具体工厂和抽象工厂的源代码。抽象工厂模式对添加产品族是符合开闭原则的,但是对修改产品等级结构(也就是说修改,添加新的产品到产品族中)是不符合开闭原则的。开闭原则的倾斜性

工厂方法模式没有所谓产品族的实现,如果要添加新的产品族,需要添加一系列的工厂和产品,且这些工厂和产品间在代码上是没有直接关系的,需要提供额外约束(要求用户只使用同一个产品族的产品),增加维护成本。但是工厂方法模式对于修改产品等级结构是符合开闭原则的——只需要创建新的产品和工厂类即可。

这个设计模式是最具抽象性和一般性的,我觉得我的描述也挺抽象的。

例子

书中的例子还满贴切的,可以直接用——

考虑这样的情景——要给软件添加“皮肤”的功能,现在假设有 Spring 皮肤和 Summer 皮肤,设计者先使用工厂方法进行系统设计——

用户应当一次选择一种皮肤——也就是说使用同一族的产品(SummerTextField,SummerButton……),而使用工厂方法,用户要选择逐个配置选择哪个具体工厂,当然也可以使用字符串拼接+反射等方式来进行约束,但是仍旧是比较复杂的(而且抽象,难以调试,维护)。且每次添加新的皮肤(产品族),需要对每一个产品都新增一个具体工厂,增加了类的数量。

这里其实拿不同操作系统的 UI 为例更好,这年代,谁拿 Java 写 UI 啊(

考虑到有添加新皮肤(新的产品族)的需求,没有添加新的组件(新的产品等级结构)的需求,使用抽象工厂模式进行重构——

其代码如下——

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 抽象工厂
public abstract class SkinFactory {
public abstract Button createButton();
public abstract TextField createTextField();
public abstract ComboBox createComboBox();
public static SkinFactory getSkinFactory(String type) {
try {
return (SkinFactory) Class.forName(type).newInstance();
}
catch (Exception e) { // 应该进一步处理
e.printStackTrace();
return null;
}
}
}

// 各个组件的接口
abstract class Button {
public abstract void displayButton();
}
abstract class TextField {
public abstract void displayTextField();
}
abstract class ComboBox {
public abstract void displayComboBox();
}

// ---------具体工厂和具体组件----------
//SummerSkinFactory
public class SummerSkinFactory extends SkinFactory {
public Button createButton() {
return new SummerButton();
}

public TextField createTextField() {
return new SummerTextField();
}

public ComboBox createComboBox() {
return new SummerComboBox();
}
}

class SummerTextField extends TextField {
public void displayTextField() {
System.out.println("一个 Summer 皮肤的文本框");
}
}
class SummerButton extends Button {
public void displayButton() {
System.out.println("一个 Summer 皮肤的按钮");
}
}
class SummerComboBox extends ComboBox {
public void displayComboBox() {
System.out.println("一个 Summer 皮肤的复选框");
}
}

//SpringSkinFactory 和 Summer 的基本一致,不复制了

// 客户端
public class Main {
public static void main(String[] args) {
SkinFactory factory = SkinFactory.getSkinFactory("SpringSkinFactory");
factory.createButton().displayButton();
factory.createComboBox().displayComboBox();
factory.createTextField().displayTextField();
}
}

要添加和使用新皮肤?没问题,添加新的具体的皮肤工厂和相应的组件即可。添加新组件?emmmm,大刀阔斧地改吧。优缺点我觉得已经说够了。

抽象工厂模式的一般形式如图——

做一做题目

两个题目——提供一个程序,它能读取不同编码的图像文件,需保证扩展性和灵活性;针对不同操作系统平台,提供一套游戏操作控制类(OperatonController)和游戏界面控制类(InterfaceController),需封装这些类的初始化过程。

第一个题目显然适合用工厂方法模式,假设图像的编码总是符合它的后缀,对所有有多后缀的编码格式,都假设它是其中一个编码——比如对 jpeg 和 jpg,假设只有 jpg。

代码如下,这里 ImageReader 是抽象工厂,JPGReader 和 PNGReader 则是具体工厂,Image 是抽象产品,JPG,PNG 是具体产品,它们理应返回 Image 作为产品,考虑到要根据后缀选择工厂,所以后缀应当作为工厂的一个域(这恐怕在实际生产中是不合理的,应当掩盖掉具体工厂的构造,直接返回 Image)——

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
abstract class ImageReader {
String path;
public ImageReader(String path){
this.path = path;
}
public abstract Image readImage();
public static ImageReader getReader(String path) {
String[] split = path.split("\\.");
String postfix = split[split.length-1].toUpperCase();
try {
return (ImageReader) Class.forName(postfix + "Reader").getConstructor(String.class).newInstance(path);
}
catch (Exception e) { // 应该进一步处理
e.printStackTrace();
return null;
}
}
}

abstract class Image {
String path;
public Image(String path) {
this.path = path;
}
public abstract void show();
}

class JPGReader extends ImageReader {
public JPGReader(String path) {
super(path);
}
public Image readImage() {
System.out.println("读取 JPG 文件:" + path);
return new JPG(path);
}
}

class JPG extends Image {

public JPG(String path) {
super(path);
}

@Override
public void show() {
System.out.println("展示这个 JPG 的信息");
}

}

class PNGReader extends ImageReader {

public PNGReader(String path) {
super(path);
}

@Override
public PNG readImage() {
System.out.println("读取 PNG 文件:" + path);
return new PNG(path);
}

}

class PNG extends Image {

public PNG(String path) {
super(path);
}

@Override
public void show() {
System.out.println("展示这个 PNG 的信息");
}

}

public class Main {
public static void main(String[] args) {
String path = "/home/abc/hello.jpg";
ImageReader reader = ImageReader.getReader(path); // 使用 ImageReader 直接返回图像在这里更合理些
Image Image = reader.readImage();
Image.show();
}
}

更加合适的解决方案是这样——

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
abstract class ImageReader {
String path;
public ImageReader(String path){
this.path = path;
}
public abstract Image readImage();
public static Image getImage(String path) {
String[] split = path.split("\\.");
String postfix = split[split.length-1].toUpperCase();
try {
return ((ImageReader) Class.forName(postfix + "Reader").getConstructor(String.class).newInstance(path)).readImage();
}
catch (Exception e) { // 应该进一步处理
e.printStackTrace();
return null;
}
}
}

public class Main {
public static void main(String[] args) {
String path = "/home/abc/hello.jpg";
Image image = ImageReader.getImage(path);
image.show();
}
}

第二个题目,每个操作系统下的各种组件显然是属于一个产品族的,这里显然要使用抽象工厂模式。角色分类如下——ControllerFactory 为抽象工厂类,定义 createOperatonController 和 createInterfaceController 抽象方法。它们返回 OperatonController 和 InterfaceController,这是两个抽象产品,它们属于一个产品族。AndroidControllerFactory 和 IOSControllerFactory 为两个具体工厂,它们生产 AndroidOperatonController,AndroidInterfaceController 和 IOSOperatonController,IOSInterfaceController 这四个具体产品。

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// 抽象层
abstract class ControllerFactory {
public abstract OperationController createOperationController();
public abstract InterfaceController createInterfaceController();
public static ControllerFactory getControllerFactory(String systemType) {
try {
return (ControllerFactory) Class.forName(systemType + "ControllerFactory").newInstance();
}
catch (Exception e) { // 应该进一步处理
e.printStackTrace();
return null;
}
}
}
abstract class OperationController {
public abstract void doSomething();
}
abstract class InterfaceController {
public abstract void doSomething();
}

// Android 的具体工厂和产品族
class AndroidControllerFactory extends ControllerFactory{

public OperationController createOperationController() {
return new AndroidOperationController();
}

public InterfaceController createInterfaceController() {
return new AndroidInterfaceController();
}
}
class AndroidOperationController extends OperationController {
public void doSomething() {
System.out.println("初始化 Android 的操作控制类");
}
}
class AndroidInterfaceController extends InterfaceController {
public void doSomething() {
System.out.println("初始化 Android 的界面控制类");
}
}

// IOS 的具体工厂和产品族
class IOSControllerFactory extends ControllerFactory{

public OperationController createOperationController() {
return new IOSOperationController();
}

public InterfaceController createInterfaceController() {
return new IOSInterfaceController();
}
}
class IOSOperationController extends OperationController {
public void doSomething() {
System.out.println("初始化 IOS 的操作控制类");
}
}
class IOSInterfaceController extends InterfaceController {
public void doSomething() {
System.out.println("初始化 IOS 的界面控制类");
}
}

// 客户端
public class Main {
public static void main(String[] args) {
ControllerFactory factory = ControllerFactory.getControllerFactory("IOS");
factory.createInterfaceController().doSomething();
factory.createOperationController().doSomething();
}
}

挺复杂,但其实也不太复杂,重要的是如何将其运用到具体开发中。