我们都知道设计模式实际上是一些指导思想,这些指导思想是由前人总结和提炼出来的,主要目的是为了解决在代码设计和维护时暴露出来的问题。这些问题往往围绕着耦合性、扩展性等展开。
一如,本篇文章所讨论的工厂方法模式,就是为了解决实际问题而存在。可以说,一种设计模式的诞生就是为了解决一类特定的问题。下面将通过一个例子来介绍工厂方法模式是如何诞生的。
演化历程
今有如下需求:
根据实际需要,将 Java 对象转换成通用的数据交互格式,并写入到磁盘中,主要有 json、xml 数据格式
根据面向对象设计原则,我们很容易想到给每一种数据交互格式提供一个数据转换器类,用抽象的数据转换器类来约束规范所有的转换器实现。在抽象的转换器的构造器中初始化组件(组件用于转换格式的时候用到的外部依赖工具),并提供一个模板方法,该模板方法中主要包含两个步骤:①转换格式,②存储数据到磁盘。转换格式由具体的转换器负责实现;存储数据到磁盘为通用的功能,可在抽象类中定义默认的实现,具体的转换器可根据实际决定是否重写。类图如下所示:
一般我们习惯将上面的转换器称呼为产品。客户端需要用到转换器,再根据需要创建转换器产品。例如:
public class Client {
public static void main(String[] args) throws IOException {
Map<String, Object> map = new HashMap<>();
map.put("name", "Jack");
map.put("age", 23);
FormatConverter formatConverter = new JsonFormatConverter(new ObjectMapper());
formatConverter.convertAndStore(map, "xxxx/1.json");
}
}
到此为止,我们已经实现了各个转换器之间的相互隔离,且用模板方法模式来封装各个转换器之间相同的部分,扩展不同的部分(点击了解 更多关于模板方法模式的内容)。
但我们同样也注意到:
- Client 在使用 FormatConverter 产品时,需要自己先创建产品,同时还创建了一个对客户端来说并不关心的组件对象 —— ObjectMapper;
如果需要创建产品的地方不止一处,那就必然要在各个需要的地方都写上创建 FormatConverter 产品的代码。
屏蔽产品创建细节
为了解决这两个问题,我们可以引入静态工厂来将产品创建的细节进行封装。既然客户端不关心产品的创建细节,也不在乎产品的组成部分有哪些,那么我们就把产品的构建过程独立出来。用一个类方法单独处理产品的创建,根据参数决定创建产品的种类。如下代码所示: ```java public class SimpleConverterFactory {
public static FormatConverter createFactory(String type) {
switch (type) {
case "json":
return new JsonFormatConverter(new ObjectMapper());
case "xml":
return new XmlFormatConverter(null);
default:
throw new RuntimeException("不支持的类型");
}
} }
_因为这个类专用于生产具体的产品,所以可形象的称呼为工厂;通常情况下,这个方法是静态方法,所以称呼为静态工厂。_<br />在静态工厂中,我们根据参数的值决定生产的产品的类型。自此,客户端可根据静态方法生产具体的产品,只需要给定相应的参数,屏蔽了产品生产的细节。
```java
public class Client {
public static void main(String[] args) throws IOException {
Map<String, Object> map = new HashMap<>();
map.put("name", "Jack");
map.put("age", 23);
// FormatConverter formatConverter = new JsonFormatConverter(new ObjectMapper());
FormatConverter formatConverter = SimpleConverterFactory.createFactory("json");
formatConverter.convertAndStore(map, "xxxx/1.json");
}
}
这样看起来已经足够优雅了,在代码服役了一段时间后,接到了这样的一个需求:
现在需要增加一个 csv 数据格式的实现,且将来逐步会提供更多的数据格式支持。
我们在实现这个功能的时候,发现了这样的现象:添加一种数据格式的支持(产品),不仅要添加产品实现类,而且要在静态方法中增加一个新的条件分支。添加新的产品实现类是无可厚非的,那有没有办法不用修改现有类的代码呢?
改变产品生产模式
要解决上面的问题,先得搞清楚静态工厂为什么添加一个新的产品就必须要改变静态方法?
因为产品的生产都是在工厂实现的,而工厂只有一个,这就导致这个工厂就像是集散中心,所有的产品不管来自于哪里,都得经过这个集散中心的周转,正是因为这,才导致我们添加一个新产品类型的同时不得不在静态方法中为这个新产品适配一个新的生产方式。
明白了这一点,我们就知道如何调整这个模式来实现我们的目的了:添加新产品类型和为新产品配置一个新的生产方式是必然的,因为产品的生产方式和产品总是相关联的,我们只需要稍微改变一下 —— 把产品由集中创建的模式改为为每个产品配置一个工厂,耦合自然也就解开了。
那么分析了这么多,具体怎么实现呢?
工厂方法模式
按照我们上面分析的思路,为每一个产品单独配置一个工厂,这样客户端就不需要再通过集中生产的方式来生产产品,这就是工厂方法模式。在工厂方法模式中,客户端在添加新的产品种类时,我们只需要告诉其所需新产品对应的工厂名字就可以生产新的产品对象。
在上面的讨论中,我们已经为每个产品提供了一个工厂,而所有的工厂都是在干同样的事情——生产产品,所以,必然有一个抽象工厂来约束所有的工厂实现。
类图分析
我们沿着刚刚的分析去实现,会得到下面这样的类图:
在这样的工作模式下,即便新增一种产品类型,我们只需要新增一个产品实现类,和一个匹配的产品工厂就能满足客户端需要。
例如:新增 csv 数据格式的支持。 新增一个产品实现(CsvFormatConverter),为这个产品配备一个工厂(CsvConverterFactory),客户端在需要使用新产品的对方只需要
FormatConverter csvConverter = new CsvConverterFactory().createConverter();
即可生产新的产品,并不需要改动使用原有产品处的代码。
演化历程回顾
到此为止,我们已历经了三个阶段:
- 第一阶段 -> 第二阶段:通过封装产品生产的过程,来对客户端屏蔽产品生产时的细节,这让整个设计对客户端更加友好,毕竟客户端并不关心工厂是如何生产产品的,客户端只需要获取一个产品;
第二阶段 —> 第三节点:通过改变产品的生产模式进行解耦,将原来集中生产模式改变为一个产品配备一个工厂,一个工厂只能生产一种产品,客户端根据实际需要选择工厂就能得到产品,这个演变过程主要是为了得到更好的扩展性,保证在新增产品种类时,不影响到现有产品的生产过程。
代码实现
抽象产品
public abstract class FormatConverter {
/**
* 转换格式所需的依赖组件,比如转换为json格式需要依赖<ObjectMapper>组件
*/
protected Object component;
public Object getComponent() {
return component;
}
/**
* 为转换器初始化一些组件
* @param component 组件
*/
public FormatConverter(Object component) {
this.component = component;
}
/**
* 转换并存储到磁盘
* @param source 源对象
* @param filename 存储文件名
*/
public final void convertAndStore(Object source, String filename) throws IOException {
String targetText = this.convert(source);
this.store(targetText, filename);
}
/**
* 转换格式
* @param source 源对象
* @return 转换后的对象
* @throws IOException IOException
*/
protected abstract String convert(Object source) throws IOException;
/**
* 存储到磁盘
* @param target 转换后的内容
* @param filename 存储文件名
*/
protected void store(String target, String filename) throws IOException {
try (FileWriter writer = new FileWriter(filename)) {
writer.write(target);
}
}
}
具体产品
public class JsonFormatConverter extends FormatConverter {
public JsonFormatConverter(Object component) {
super(component);
}
@Override
protected String convert(Object source) throws IOException {
System.out.println("|==> 即将开始转换对象为JSON格式 ---------------------------------|");
ObjectMapper mapper = (ObjectMapper) super.getComponent();
String tar = mapper.writeValueAsString(source);
System.out.println(" 转换后内容:" + tar);
return tar;
}
}
public class XmlFormatConverter extends FormatConverter {
public XmlFormatConverter(Object component) {
super(component);
}
@Override
protected String convert(Object source) {
System.out.println("|==> 即将开始转换对象为XML格式 ---------------------------------|");
// todo 在此处实现转换xml格式
return null;
}
}
抽象工厂
```java public interface ConverterFactory {
/**
- 生产转换器
- @return FormatConverter */ FormatConverter createConverter(); }
<a name="mHlpr"></a>
## 具体工厂
```java
public class JsonConverterFactory implements ConverterFactory{
@Override
public FormatConverter createConverter() {
return new JsonFormatConverter(new ObjectMapper());
}
}
public class XmlConverterFactory implements ConverterFactory{
@Override
public FormatConverter createConverter() {
// todo 在此处构造转换器所需的组件
return new XmlFormatConverter(null);
}
}
客户端
public class Client {
public static void main(String[] args) throws IOException {
Map<String, Object> map = new HashMap<>();
map.put("name", "Jack");
map.put("age", 23);
FormatConverter converter = new JsonConverterFactory().createConverter();
converter.convertAndStore(map, "***/1.json");
}
}
|==> 即将开始转换对象为JSON格式 ---------------------------------|
转换后内容:{"name":"Jack","age":23}
总结及扩展
模式定义
通过上面的例子,我们已经了解了工厂方法模式,接下来是关于该模式的定义:
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
该定义较容易理解,定义一个用于创建对象的接口(抽象工厂中的生产产品的方法),让子类决定实例化哪一个类(把产品的生产过程交给具体的工厂实现,各个具体的工厂生产具体的产品)。
标准结构类图
角色列表:
- Product: 抽象产品,定义工厂方法所创建的对象的接口。
- ConcreteProduct: 具体产品,实现 Product 接口。
- Creator: 声明工厂方法,该方法返回一个 Product 类型的对象( Creator 也可以定义一个工厂方法的缺省实现,它返回一个默认的 ConcreteProduct 对象)。
ConcreteCreator: 重定义工厂方法以返回一个具体的 ConcreteProduct 实例。
源码中的应用
(1)在 jdk 源码中,java.util.Calendar 使用静态工厂来生产 Calendar 的实例
private static Calendar createCalendar(TimeZone zone,Locale aLocale){
CalendarProvider provider =
LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
.getCalendarProvider();
if (provider != null) {
try {
// 1 ----------------------------------
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
}
}
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
// 2 ----------------------------------
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
// 3 ----------------------------------
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
// 4 ----------------------------------
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
if (cal == null) {
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
// 5 ----------------------------------
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
// 6 ----------------------------------
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
// 7 ----------------------------------
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}
(2)在 spring 源码中,FactoryBean 使用工厂方法 getObject() 来生产对象实例
public interface FactoryBean<T> {
/**
* 返回此工厂管理的对象的实例(可能是共享的或独立的)......
* @return an instance of the bean (can be {@code null})
* @throws Exception in case of creation errors
* @see FactoryBeanNotInitializedException
*/
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}