基础知识回顾
工厂模式包括:简单工厂(不在23种设计模式中)、工厂方法和抽象工厂。
简单工厂
结构
定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类。
- 抽象类或接口:定义了要创建的产品对象的接口。
- 具体实现:具有统一父类的具体类型的产品。
- 产品工厂:负责创建产品对象。工厂模式同样体现了开闭原则,将“创建具体的产品实现类”这部分变化的代码从不变化的代码“使用产品”中分离出来,之后想要新增产品时,只需要扩展工厂的实现即可。
使用
现在举一个文件解析的例子 ```java
public class RuleConfigSource { public RuleConfig load(String ruleConfigFilePath) { String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFileExtension); if (parser == null) { throw new InvalidRuleConfigException( “Rule config file format is not supported: “ + ruleConfigFilePath); }
String configText = "";
//从ruleConfigFilePath文件中读取配置文本到configText中
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) { //…解析文件名获取扩展名,比如rule.json,返回json return “json”; } }
public class RuleConfigParserFactory { public static IRuleConfigParser createParser(String configFormat) { IRuleConfigParser parser = null; if (“json”.equalsIgnoreCase(configFormat)) { parser = new JsonRuleConfigParser(); } else if (“xml”.equalsIgnoreCase(configFormat)) { parser = new XmlRuleConfigParser(); } else if (“yaml”.equalsIgnoreCase(configFormat)) { parser = new YamlRuleConfigParser(); } else if (“properties”.equalsIgnoreCase(configFormat)) { parser = new PropertiesRuleConfigParser(); } return parser; } }
在上面的代码实现中,我们每次调用 RuleConfigParserFactory 的 createParser() 的时候,都要创建一个新的 parser。实际上,如果 parser 可以复用,为了节省内存和对象创建的时间,我们可以将 parser 事先创建好缓存起来。当调用 createParser() 函数的时候,我们从缓存中取出 parser 对象直接使用。
```java
/**
* 优化版本
*/
public class RuleConfigParserFactory {
private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<>();
static {
cachedParsers.put("json", new JsonRuleConfigParser());
cachedParsers.put("xml", new XmlRuleConfigParser());
cachedParsers.put("yaml", new YamlRuleConfigParser());
cachedParsers.put("properties", new PropertiesRuleConfigParser());
}
public static IRuleConfigParser createParser(String configFormat) {
if (configFormat == null || configFormat.isEmpty()) {
return null;//返回null还是IllegalArgumentException全凭你自己说了算
}
IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());
return parser;
}
}
缺陷
上面的工厂实现是一个具体的类RuleConfigParserFactory,而非接口或者抽象类,createParser()方法利用if-else创建并返回具体的解析类,如果增加新的解析类类,工厂的创建方法中就要增加新的if-else。这种做法扩展性差,违背了开闭原则,也影响了可读性。所以,这种方式使用在业务较简单,工厂类不会经常更改的情况。
工厂方法
为了解决上面提到的”增加if-else”的问题,可以为每一个键盘子类建立一个对应的工厂子类,这些工厂子类实现同一个抽象工厂接口。这样,创建不同品牌的键盘,只需要实现不同的工厂子类。当有新品牌加入时,新建具体工厂继承抽象工厂,而不用修改任何一个类
结构
- 抽象工厂:声明了工厂方法的接口。
- 具体产品工厂:实现工厂方法的接口,负责创建产品对象。
- 产品抽象类或接口:定义工厂方法所创建的产品对象的接口。
- 具体产品实现:具有统一父类的具体类型的产品。
使用
```java
public interface IRuleConfigParserFactory { IRuleConfigParser createParser(); }
public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser() { return new JsonRuleConfigParser(); } }
public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser() { return new XmlRuleConfigParser(); } }
public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser() { return new YamlRuleConfigParser(); } }
public class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser() { return new PropertiesRuleConfigParser(); } }
实际上,这就是工厂方法模式的典型代码实现。这样当我们新增一种 parser 的时候,只需要新增一个实现了 IRuleConfigParserFactory 接口的 Factory 类即可。所以,工厂方法模式比起简单工厂模式更加符合开闭原则。<br />接下来,我们看一下,如何用这些工厂类来实现 RuleConfigSource 的 load() 函数。具体的代码如下所示:
```java
public class RuleConfigSource {
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
IRuleConfigParserFactory parserFactory = null;
if ("json".equalsIgnoreCase(ruleConfigFileExtension)) {
parserFactory = new JsonRuleConfigParserFactory();
} else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) {
parserFactory = new XmlRuleConfigParserFactory();
} else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) {
parserFactory = new YamlRuleConfigParserFactory();
} else if ("properties".equalsIgnoreCase(ruleConfigFileExtension)) {
parserFactory = new PropertiesRuleConfigParserFactory();
} else {
throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);
}
IRuleConfigParser parser = parserFactory.createParser();
String configText = "";
//从ruleConfigFilePath文件中读取配置文本到configText中
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名获取扩展名,比如rule.json,返回json
return "json";
}
}
从上面的代码实现来看,工厂类对象的创建逻辑又耦合进了 load() 函数中,跟我们最初的代码版本非常相似,引入工厂方法非但没有解决问题,反倒让设计变得更加复杂了。那怎么来解决这个问题呢?我们可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象。这段话听起来有点绕,我把代码实现出来了,你一看就能明白了。其中,RuleConfigParserFactoryMap 类是创建工厂对象的工厂类,getParserFactory() 返回的是缓存好的单例工厂对象。
public class RuleConfigSource {
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
IRuleConfigParserFactory parserFactory = RuleConfigParserFactoryMap.getParserFactory(ruleConfigFileExtension);
if (parserFactory == null) {
throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);
}
IRuleConfigParser parser = parserFactory.createParser();
String configText = "";
//从ruleConfigFilePath文件中读取配置文本到configText中
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名获取扩展名,比如rule.json,返回json
return "json";
}
}
//因为工厂类只包含方法,不包含成员变量,完全可以复用,
//不需要每次都创建新的工厂类对象,所以,简单工厂模式的第二种实现思路更加合适。
public class RuleConfigParserFactoryMap { //工厂的工厂
private static final Map<String, IRuleConfigParserFactory> cachedFactories = new HashMap<>();
static {
cachedFactories.put("json", new JsonRuleConfigParserFactory());
cachedFactories.put("xml", new XmlRuleConfigParserFactory());
cachedFactories.put("yaml", new YamlRuleConfigParserFactory());
cachedFactories.put("properties", new PropertiesRuleConfigParserFactory());
}
public static IRuleConfigParserFactory getParserFactory(String type) {
if (type == null || type.isEmpty()) {
return null;
}
IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase());
return parserFactory;
}
}
当我们需要添加新的规则配置解析器的时候,我们只需要创建新的 parser 类和 parser factory 类,并且在 RuleConfigParserFactoryMap 类中,将新的 parser factory 对象添加到 cachedFactories 中即可。代码的改动非常少,基本上符合开闭原则。<br /> 实际上,对于规则配置文件解析这个应用场景来说,工厂模式需要额外创建诸多 Factory 类,也会增加代码的复杂性,而且,每个 Factory 类只是做简单的 new 操作,功能非常单薄(只有一行代码),也没必要设计成独立的类,所以,在这个应用场景下,简单工厂模式简单好用,比工厂方法模式更加合适。<br /> 那什么时候该用工厂方法模式,而非简单工厂模式呢?
- 当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。
- 在某些场景下,如果对象不可复用,那工厂类每次都要返回不同的对象。如果我们使用简单工厂模式来实现,就只能选择第一种包含 if 分支逻辑的实现方式。如果我们还想避免烦人的 if-else 分支逻辑,这个时候,我们就推荐使用工厂方法模式。
抽象工厂
结构
为了缩减工厂实现子类的数量,不必给每一个产品分配一个工厂类,可以将产品进行分组,每组中的不同产品由同一个工厂类的不同方法来创建。
- 抽象工厂:声明了创建抽象产品对象的操作接口。
- 具体产品工厂:实现了抽象工厂的接口,负责创建产品对象。
- 产品抽象类或接口:定义一类产品对象的接口。
- 具体产品实现:定义一个将被相应具体工厂创建的产品对象。
使用
在简单工厂和工厂方法中,类只有一种分类方式。比如,在规则配置解析那个例子中,解析器类只会根据配置文件格式(Json、Xml、Yaml……)来分类。但是,如果类有两种分类方式,比如,我们既可以按照配置文件格式来分类,也可以按照解析的对象(Rule 规则配置还是 System 系统配置)来分类,那就会对应下面这 8 个 parser 类。如果我们未来还需要增加针对业务配置的解析器(比如 IBizConfigParser),那就要再对应地增加 4 个工厂类。 ```java
针对规则配置的解析器:基于接口IRuleConfigParser JsonRuleConfigParser XmlRuleConfigParser YamlRuleConfigParser PropertiesRuleConfigParser
针对系统配置的解析器:基于接口ISystemConfigParser JsonSystemConfigParser XmlSystemConfigParser YamlSystemConfigParser PropertiesSystemConfigParser
我们可以让一个工厂负责创建多个不同类型的对象(IRuleConfigParser、ISystemConfigParser 等),而不是只创建一种 parser 对象。这样就可以有效地减少工厂类的个数。具体的代码实现如下所示:
```java
public interface IConfigParserFactory {
IRuleConfigParser createRuleParser();
ISystemConfigParser createSystemParser();
//此处可以扩展新的parser类型,比如IBizConfigParser
}
public class JsonConfigParserFactory implements IConfigParserFactory {
@Override
public IRuleConfigParser createRuleParser() {
return new JsonRuleConfigParser();
}
@Override
public ISystemConfigParser createSystemParser() {
return new JsonSystemConfigParser();
}
}
public class XmlConfigParserFactory implements IConfigParserFactory {
@Override
public IRuleConfigParser createRuleParser() {
return new XmlRuleConfigParser();
}
@Override
public ISystemConfigParser createSystemParser() {
return new XmlSystemConfigParser();
}
}
// 省略YamlConfigParserFactory和PropertiesConfigParserFactory代码
优缺点
Spring下的最佳实践
工厂模式一般配合策略模式一起使用,当系统中有多种产品(策略),且每种产品有多个实例时,此时适合使用工厂模式:每种产品对应的工厂提供该产品不同实例的创建功能,从而避免调用方和产品创建逻辑的耦合,完美符合迪米特法则(最少知道原则)。
使用工厂方法模式
背景
在平常开发中,我们经常会在 Spring 中实现诸如这样的功能:收集某一类具有共同特征的 Bean(都实现了某个接口或者都打上了某个注解等),然后放入容器中(一般是 Map),使用的时候根据 Bean 的标识,来获取到对应的 Bean。
通过表单标识获得表单对应提交处理器的 FormDataHandlerFactory:
@Component
public class FormDataHandlerFactory {
private static final
Map<String, FormDataHandler> FORM_DATA_HANDLER_MAP = new HashMap<>(16);
/**
* 根据表单标识,获取对应的 Handler
*
* @param formCode 表单标识
* @return 表单对应的 Handler
*/
public FormDataHandler getHandler(String formCode) {
return FORM_DATA_HANDLER_MAP.get(formCode);
}
@Autowired
public void setFormDataHandlers(List<FormDataHandler> handlers) {
for (FormDataHandler handler : handlers) {
FORM_DATA_HANDLER_MAP.put(handler.getFormCode(), handler);
}
}
}
通过表单项类型获得表单项转换器的 FormItemConverterFactory:
@Component
public class FormItemConverterFactory {
private static final
EnumMap<FormItemTypeEnum, FormItemConverter> CONVERTER_MAP = new EnumMap<>(FormItemTypeEnum.class);
/**
* 根据表单项类型获得对应的转换器
*
* @param type 表单项类型
* @return 表单项转换器
*/
public FormItemConverter getConverter(FormItemTypeEnum type) {
return CONVERTER_MAP.get(type);
}
@Autowired
public void setConverters(List<FormItemConverter> converters) {
for (final FormItemConverter converter : converters) {
CONVERTER_MAP.put(converter.getType(), converter);
}
}
}
上面两段代码,其实就是基于Spring的简单工厂模式,如果再来一个业务类型就要再新增一个工厂类,我们下面尝试基于Spring优化一下
方案
对于工厂方法模式来说,变化的是产品、工厂,因而我们可以先定义出抽象的产品和抽象的工厂。
抽象的产品(策略):
public interface Strategy<T> {
/**
* 获得策略的标识
*/
T getId();
}
每个产品必须实现 Strategy 接口,代表每个产品必须有一个唯一的标识。
抽象的策略工厂:
public abstract class StrategyFactory<T, S extends Strategy<T>>
implements InitializingBean, ApplicationContextAware {
private Map<T, S> strategyMap;
private ApplicationContext appContext;
/**
* 根据策略 id 获得对应的策略的 Bean
*
* @param id 策略 id
* @return 策略的 Bean
*/
public S getStrategy(T id) {
return strategyMap.get(id);
}
/**
* 获取策略的类型(交给子类去实现)
*
* @return 策略的类型
*/
protected abstract Class<S> getStrategyType();
@Override
public void afterPropertiesSet() {
// 获取 Spring 容器中,所有 S 类型的 Bean
Collection<S> strategies = appContext.getBeansOfType(getStrategyType()).values();
strategyMap = Maps.newHashMapWithExpectedSize(strategies.size());
// 将所有 S 类型的 Bean 放入到 strategyMap 中
for (final S strategy : strategies) {
T id = strategy.getId();
strategyMap.put(id, strategy);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
appContext = applicationContext;
}
}
Spring 容器在启动的时候,会去扫描工厂指定的类型(Class)的 Bean,并将其注册到工厂中(加入到 strategyMap)。所以对于工厂中产品的生产过程,借助 Spring。
接下来基于我们的抽象产品和抽象工厂,我们重构上面的两个 Factory:
通过表单标识获得表单对应提交处理器的 FormDataHandlerFactory
@Component
public class FormDataHandlerFactory extends StrategyFactory<String, FormDataHandler> {
@Override
protected Class<FormDataHandler> getStrategyType() {
return FormDataHandler.class;
}
}
FormDataHandlerFactory 只需要指定一下其产品类型为 FormDataHandler。当然,FormDataHandler 我们也需要改造一下:
public interface FormDataHandler extends Strategy<String> {
@Override
default String getId() { return getFormCode(); }
String getFormCode();
CommonResponse<Object> submit(FormSubmitRequest request);
}
通过表单项类型获得表单项转换器的 FormItemConverterFactory
@Component
public class FormItemConverterFactory extends StrategyFactory<FormItemTypeEnum, FormItemConverter> {
@Override
protected Class<FormItemConverter> getStrategyType() {
return FormItemConverter.class;
}
}
此时,FormItemConverterFactory 也只需要指定一下产品的类型,不再会写重复代码。同理,需要改造一下 FormItemConverter:
public interface FormItemConverter extends Strategy<FormItemTypeEnum> {
@Override
default FormItemTypeEnum getId() { return getType(); }
FormItemTypeEnum getType();
FormItem convert(FormItemConfig config);
}
如果这个时候新加一个 通过列表标识获得列表数据拉取器的 ListDataFetcherFactory,那么首先定义出获取列表数据的接口(产品):
public interface ListDataFetcher extends Strategy<String> {
CommonResponse<JSONObject> fetchData(ListDataFetchRequest request);
}
然后再实现 ListDataFetcherFactory(工厂):
@Component
public class ListDataFetcherFactory extends StrategyFactory<String, ListDataFetcher> {
@Override
protected Class<ListDataFetcher> getStrategyType() {
return ListDataFetcher.class;
}
}
通过抽象产品 Strategy 和抽象工厂 StrategyFactory,我们的代码完美符合了 DRY 原则(Don’t Repeat Yourself)。
优化
借助反射
借助反射,我们还可以使得工厂代码变得更加简单:因为如果父类包含泛型参数,且子类对泛型参数进行了具体化,那么这个具体化的泛型类型,可在运行时获取到。基于这个特性,我们可以改造 StrategyFactory:
public abstract class StrategyFactory<T, S extends Strategy<T>>
implements InitializingBean, ApplicationContextAware {
...
/**
* 通过反射获取策略的类型
*
* @return 策略的类型
*/
protected Class<S> getStrategyType() {
// getClass 获取当前运行时实例的类,getGenericSuperclass 获得泛型父类
Type superclass = getClass().getGenericSuperclass();
ParameterizedType pt = (ParameterizedType) superclass;
Type[] actualTypeArguments = pt.getActualTypeArguments();
// 获得索引为 1 的实际参数类型,即第二个实际参数的类型
Type actualTypeArgument = actualTypeArguments[1];
@SuppressWarnings("unchecked")
Class<S> result = (Class<S>) actualTypeArgument;
return result;
}
...
}
那么上面三个 Factory 写起来就更简单了:
@Component
public class FormDataHandlerFactory extends StrategyFactory<String, FormDataHandler> {
}
@Component
public class FormItemConverterFactory extends StrategyFactory<FormItemTypeEnum, FormItemConverter> {
}
@Component
public class ListDataFetcherFactory extends StrategyFactory<String, ListDataFetcher> {
}
组合优先于继承
上述的方案是通过继承,并借助泛型的反射功能,由子类来指定策略( S getStrategyType)的类型。如果工厂类型较多,那么每次新加一个工厂类,容易导致 “类爆炸”。对于上述的方案,变化的部分就是策略的类型,除了继承,我们还可以通过组合来解决这个变化。修改我们的 StrategyFactory:
public class StrategyFactory<T, S extends Strategy<T>>
implements InitializingBean, ApplicationContextAware {
private final Class<S> strategyType;
private Map<T, S> strategyMap;
private ApplicationContext appContext;
/**
* 创建一个策略工厂
*
* @param strategyType 策略的类型
*/
public StrategyFactory(Class<S> strategyType) {
this.strategyType = strategyType;
}
/**
* 根据策略 id 获得对应的策略的 Bean
*
* @param id 策略 id
* @return 策略的 Bean
*/
public S getStrategy(T id) {
return strategyMap.get(id);
}
@Override
public void afterPropertiesSet() {
// 获取 Spring 容器中,所有 S 类型的 Bean
Collection<S> strategies = appContext.getBeansOfType(strategyType).values();
strategyMap = Maps.newHashMapWithExpectedSize(strategies.size());
// 将 所有 S 类型的 Bean 放入到 strategyMap 中
for (final S strategy : strategies) {
T id = strategy.getId();
strategyMap.put(id, strategy);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
appContext = applicationContext;
}
}
此时 StrategyFactory 不再是抽象类,并且为 StrategyFactory 引入一个新的属性 strategyType,并且在构造 StrategyFactory 就必须设置当前工厂中的策略(产品)类型。那么对于 FormDataHandlerFactory、FormItemConverterFactory 和 ListDataFetcherFactory,我们不需要再通过继承产生,直接通过配置进行组合即可:
@Configuration
public class FactoryConfig {
@Bean
public StrategyFactory<String, FormDataHandler> formDataHandlerFactory() {
return new StrategyFactory<>(FormDataHandler.class);
}
@Bean
public StrategyFactory<FormItemTypeEnum, FormItemConverter> formItemConverterFactory() {
return new StrategyFactory<>(FormItemConverter.class);
}
@Bean
public StrategyFactory<String, ListDataFetcher> listDataFetcherFactory() {
return new StrategyFactory<>(ListDataFetcher.class);
}
}