创建式:工厂方法

1 基本介绍

​ 简单工厂方法模式由于每次引入新产品时,都需要通过传入参数的不同来创建不同的产品,这必定要修改工厂类的代码,违反了“开闭原则”

​ 从而出现了——工厂方法模式,就是解决该问题的

​ 工厂方法中,不再提供统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂

​ 一般是定义一个用于创建对象的接口,让子类决定将哪一个类实例化。如下图

image-20220314102322307

2 设计分析

2.1 简单工厂设计

​ 通过栗子来理解把,现在某公司要设计一个系统运行日志记录器,用户可以通过修改配置文件更改日志记录方式,那么如何设计记录器的初始化和更改日志记录方式将会是一大难点。使用简单工厂模式设计如下

image-20220314101334388

​ 这时候,LoggerFactory代码大概为这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//日志记录器工厂
class LoggerFactory {
// 静态工厂方法
public static Logger createLogger( string args){
if(args.equalsIgnoreCase ( "db")) {
//连接数据库﹐代码省略
//创建数据库日志记录器对象
Logger logger : new DatabaseLogger( );//初始化数据库日志记录器﹐代码省略
return logger;
}
else if(args.equalsignorecase( "file" )) {
//创建日志文件
//创建文件日志记录器对象
Logger logger = new FileLogger ( );//初始化文件日志记录器﹐代码省略
return logger;
}
else {
return null;
}
}
}

​ 这样设计问题很大,包含大量的if...else...代码,维护和测试难度增大,并且系统扩展不灵活

2.2 工厂方法设计

​ 使用工厂方法设计日志记录器,将会变成下面这样

image-20220314102641800

​ 代码大概如下:

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
package DesignPatterns.JavaDesign.FactoryMethod;

// 编写如下客户端测试代码∶
public class Test {
public static void main(String args[]) {
LoggerFactory factory;
Logger logger;
factory = new FileLoggerFactory(); //可引入配置文件实现
// factory = (LoggerFactory) XMLUtil.getBean();
logger = factory.createLogger();
logger.writeLog( );
}
}

// 日志记录器接口︰抽象产品
interface Logger {
public void writeLog();
}
// 数据库日志记录器︰具体产品
class DatabaseLogger implements Logger {
public void writeLog() {
System.out.println("数据库日志记录。");
}
}

// 文件日志记录器∶具体产品
class FileLogger implements Logger {
public void writeLog() {
System.out.println( "文件日志记录。");
}
}

// 日志记录器工厂接口︰抽象工厂
interface LoggerFactory {
public Logger createLogger();
}
// 数据库日志记录器工厂类∶具体工厂
class DatabaseLoggerFactory implements LoggerFactory {
public Logger createLogger() {
//连接数据库﹐代码省略//创建数据库日志记录器对象
Logger logger = new DatabaseLogger();//初始化数据库日志记录器﹐代码省略
return logger;
}
}

// 文件日志记录器工厂类∶具体工厂
class FileLoggerFactory implements LoggerFactory {
public Logger createLogger() {
//创建文件日志记录器对象
Logger logger = new FileLogger( );//创建文件·代码省略
return logger;
}
}

image-20220314104158213

添加xml配置文件优化

xml文件

1
2
3
4
<?xml version="1.0"?>
<config>
<className>DesignPatterns.JavaDesign.FactoryMethod.DatabaseLoggerFactory</className>
</config>

读取xml工具类

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
package DesignPatterns.JavaDesign.FactoryMethod;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;

public class XMLUtil {
// 该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
public static Object getBean() {
try {
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File(System.getProperty("user.dir") + "/src/main/resources/config.xml"));
// 获取包含类名的文本节点
NodeList nl = doc.getElementsByTagName("className");
Node classNode = nl.item(0).getFirstChild();
String cName = classNode.getNodeValue();
// 通过类名生成实例对象并将其返回
Class<?> c = Class.forName(cName);
Object obj = c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

修改客户端代码

1
2
3
4
5
6
7
8
9
10
11
// 编写如下客户端测试代码∶
public class Test {
public static void main(String args[]) {
LoggerFactory factory;
Logger logger;
// factory = new FileLoggerFactory(); //可引入配置文件实现
factory = (LoggerFactory) XMLUtil.getBean();
logger = factory.createLogger();
logger.writeLog( );
}
}

image-20220314105848133

2.3 其他

重载的工厂方法

image-20220314110616056

​ 通过多种方式初始化日志记录器,例如可以提供默认实现,传入文件路径,或将参数封装在一个Object类型的对象中

工厂方法的隐藏

​ 有时候需要对工厂方法函数进行隐藏

image-20220314110754654

​ 修改后的代码大致如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//改为抽象类
abstract class LoggerFactory { //在工厂类中直接调用日志记录器类的业务方法writeLog ()
public void writeLog () {
Logger logger = this.createLogger( );logger.writeLog ();
}
public abstract Logger createLogger();
...
}

class client {
public static void main( String args[]){
LoggerFactory factory;
factory = (LoggerFactory )XMLUtil.getBean( );
factory.writeLog( ); //直接使用工厂对象来调用产品对象的业务方法
}
}

3 总结

优缺点分析

优点:

  1. 工厂方法用来创建客户所需的产品,同时隐藏了哪种具体产品类被实例化这一细节,用户只需关心产品对应的工厂,甚至都无需知道具体产品类的类名
  2. 加入新产品时,无需修改抽象工厂和抽象产品提供的接口,无需修改客户端,也无需修改其他的具体工厂和具体产品,只需要添加一个具体工厂和具体产品就可以了。提高了系统的可扩展性,符合可扩展性

缺点:

  1. 添加新产品时需要添加对应工厂类和产品,类的个数成对增长,在一定程度上增加了系统复杂度
  2. 引入了抽象层,增加了系统的抽象和理解难度。且实现可能要用到DOM,反射技术,增加系统实现难度

使用场景

  1. 客户端不需要知道具体产品类的类名,只需要知道对应的工厂即可
  2. 抽象工厂类通过其子类来指定创建哪个对象

4 场景使用

4.1 Factory实例化Bean

​ FactoryBean是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口,不要与容器BeanFacoty相混淆

​ FactoryBean,其主语是Bean,定语是Factory,也就是说,它本身与其他注册到容器的对象一样,只是一个Bean而已,只不过这里类型的Bean本身就是生产对象的工厂

​ 当使用该工厂Bean时需要实现三个方法

1
2
3
4
5
6
7
8
9
10
11
12
/*
该方法返回该FactoryBean“生产”的对象。我们需要实现该方法以给出自己对象实例化逻辑
*/
public String getObject() throws Exception;
/**
该方法仅返回getObject()方法所返回的对象的类型。如果预先无法确定,则返回null
*/
public Class<?> getObjectType();
/*
该方法返回结果用于表明,getObject()“生产”的对象是否要以singleton(单例)形式存于容器中。如果以singleton形式存在,则返回true,否则返回false
*/
public boolean isSingleton();

测试

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
package DesignPatterns.JavaDesign.FactoryMethod;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
String bean = (String) context.getBean("bean");
System.out.println(bean);
}
}

class nameFactoryBean<T> implements FactoryBean<String> {
//该方法返回该FactoryBean“生产”的对象
//我们需要实现该方法以给出自己对象实例化逻辑
@Override
public String getObject() throws Exception {
return new String("w1nd");
}

//该方法仅返回getObject()方法所返回的对象的类型
//如果预先无法确定,则返回null
@Override
public Class<?> getObjectType() {
return null;
}

//该方法返回结果用于表明,getObject()“生产”的对象是否要以singleton(单例)形式存于容器中
//如果以singleton形式存在,则返回true,否则返回false
@Override
public boolean isSingleton() {
return false;
}

}

配置文件

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- FactoryBean实例化Bean -->
<bean id="bean" class="DesignPatterns.JavaDesign.FactoryMethod.nameFactoryBean"></bean>

</beans>

参考

《Java设计模式》——刘伟

(108条消息) 创建对象与使用对象——谈谈工厂的作用_刘伟技术博客-CSDN博客

(109条消息) Spring 工厂方法与FactoryBean(实例化Bean)_浅然的专栏-CSDN博客_spring 工厂bean


创建式:工厂方法
https://2w1nd.github.io/2022/03/13/设计模式/创建式:工厂方法/
作者
w1nd
发布于
2022年3月13日
许可协议