工厂模式是一种创建型模式,主要作用就是创建对象,将对象的创建过程和使用的过程进行解耦。我们平时说的工厂模式实际上是对三种不同类型的工厂模式的统称,简单工厂、工厂方法、抽象工厂,而在23种设计模式中,只定义了工厂方法和抽象工厂,将简单工厂看作是工厂方法的一种特例,本篇主要讲述的是简单工厂。
简单工厂,就像它的名字一样突出一个简单,就是将业务流程代码中直接使用new
关键字来创建对象,修改为通过一个工厂类
创建对象,这就是简单工厂。如果仅仅只是将new
操作转移到了一个新的类里面,看起来只是在徒增类的数量和代码量,并没有什么意义。
那为什么我们还要使用简单工厂模式呢?
是因为它在某些特定的场景下有其存在的价值,让我们从不同的场景来看看。
除了日常业务开发之外,有时候我们也可能得做一些框架或者工具的开发,这部分开发出来的工具需要提供给其他的开发人员使用,而他们在使用的时候,第一步就是获取到这个工具的对象,这种情况下就可以给使用者提供一个简单工厂,让使用者通过工厂来创建对象。
例如在Java
中使用日历相关的工具Calendar
就是通过简单工厂提供的获取日历对象的方法,如下图:
我们做类似的框架或工具开发的时候,完全可以参照Calendar
的方式,给使用者提供创建对象的能力,从而让使用者无需关心对象的实际创建过程,而是只需要通过特定的方法和参数,就能获取到一个可供使用的对象。
即使是在做业务相关开发,有时候也会涉及到一些相对复杂的业务对象的创建(例如DDD中的领域对象),这时候可以使用简单工厂将产品对象的创建流程从业务流程中抽离。由于创建对象的复杂性被隔离在工厂类中,因此当涉及到产品类的变化时,比如增加新功能、改变实现方式等,只需要改动工厂类,不会对使用产品的其他模块造成影响,有利于产品创建逻辑的集中管理,以及系统的维护和版本升级。
之前在做一个项目开发的时候使用过简单工厂处理过Domain
对象的创建,先说一下背景:
这个项目使用的时COLA架构(一种DDD的代码层面实践的框架),在这个架构的分层中Domain
层属于最底层,如下图中的demoWeb-domain
:
Domain
层中会完成大部分的业务逻辑,但我们知道业务流程中往往伴随着与中间件、其他服务之间的交互(例如数据库的存取操作),但这部分交互不应该由领域对象来实现。
在COLA
中的做法就是提供一个“防腐层”来实现,所谓的防腐层就是在domain
层中定义与中间件交互的interface
再交由infrastructure
来实现,这样在domain
层就只需要关心自己需要做一个什么交互,而不需要关心具体的交互实现,如下的红框所示。
背景介绍完了,说一下这里面存在一个问题,就是Domain
对象为了保证业务模型的纯洁性,一般不会使用Spring来管理领域对象的生命周期,在这种情况下如何才能将gatewayImpl
对象注入到领域对象中呢?
答案是在Infrastructure
层中,将getewayImpl
对象set到领取对象中,如果领域对象的创建过程相对复杂,就可以使用简单工厂进行创建,统一管理创建逻辑,代码如下:
@Component
public class MsgWecomAppFactory {
@Resource
private MsgWecomGateway msgWecomGateway;
@Resource
private MsgWecomCacheGateway msgWecomCacheGateway;
@Resource
private WecomHttpHelper wecomHttpHelper;
/**
* 创建企业微信应用号领域对象,用于确认消息
*
* @param agentId 应用号ID
* @return 领域对象
*/
public MsgWecomApp createMsgWecomAppForConfirm(String agentId) {
MsgWecomApp msgWecomApp = new MsgWecomApp();
// 1.查询企业微信应用配置信息
Optional<MsgWecomAppConfig> appConfig = msgWecomGateway.getWecomAppConfigByAgentId(agentId);
Assert.isTrue(appConfig.isPresent(), "企业微信应用号配置信息不存在,执行失败,agentId:" + agentId);
msgWecomApp.setConfig(appConfig.get());
msgWecomApp.setMsgWecomCacheGateway(msgWecomCacheGateway);
msgWecomApp.setMsgWecomGateway(msgWecomGateway);
msgWecomApp.setWecomHttpHelper(wecomHttpHelper);
return msgWecomApp;
}
/**
* 创建企业微信应用号领域对象,用于发送消息
*
* @param agentId 应用号ID
* @param phones 手机号
* @return 领域对象
*/
public MsgWecomApp createMsgWecomAppForSend(String agentId, Set<String> phones) {
MsgWecomApp msgWecomApp = new MsgWecomApp();
// 1.查询企业微信应用配置信息
Optional<MsgWecomAppConfig> appConfig = msgWecomGateway.getWecomAppConfigByAgentId(agentId);
Assert.isTrue(appConfig.isPresent(), "企业微信应用号配置信息不存在");
msgWecomApp.setConfig(appConfig.get());
// 2.查询用户信息,优先使用手机号查询,如果手机号为空或手机号未查询到则使用邮箱查询
List<MsgWecomMemberInfo> msgWecomMemberInfos = new ArrayList<>();
if (CollUtil.isNotEmpty(phones)) {
msgWecomMemberInfos = msgWecomGateway.listWecomMemberInfoByPhone(phones);
// 对比参数中的手机号与查询到的手机号,获取差集
List<String> existPhone = msgWecomMemberInfos.stream().map(MsgWecomMemberInfo::getPhone).collect(Collectors.toList());
msgWecomApp.setNotExistPhones(CollUtil.subtractToList(phones, existPhone));
}
msgWecomApp.setToSendMemberInfos(msgWecomMemberInfos);
msgWecomApp.setMsgWecomCacheGateway(msgWecomCacheGateway);
msgWecomApp.setMsgWecomGateway(msgWecomGateway);
msgWecomApp.setWecomHttpHelper(wecomHttpHelper);
return msgWecomApp;
}
}
策略模式中的选择器实现:
在SpringBoot优雅使用策略模式这一篇博客中提到了如何使用Spring
对Bean的管理能力,来实现策略模式的选择器。同样的,如果没有使用Spring
或者业务对象的生命周期不需要Spring
框架介入时,就可以使用简单工厂+单例的方式来实现,代码如下:
/**
* 策略选择器工厂
*/
public class StrategySelectorFactory {
private static final Map<String, Strategy> STRATEGY_MAP = new java.util.HashMap<>();
static {
STRATEGY_MAP.put("A", new StrategyA());
STRATEGY_MAP.put("B", new StrategyB());
}
public static Strategy getStrategy(String strategyKey) {
if (strategyKey == null || strategyKey.isEmpty()) {
throw new IllegalArgumentException("strategyKey can not be empty");
}
return STRATEGY_MAP.get(strategyKey);
}
}
本篇主要讲述了工厂模式中的特例:简单工厂模式,并通过3种不同的场景来介绍这种模式存在的意义,有以下几方面:
最后,虽然简单工厂模式在一定程度上提高了灵活性和可维护性,但它也有其局限性,例如违反了开闭原则,每增加一个新产品就需要修改工厂类的代码。
但瑕不掩瑜,简单工厂模式在很多简单场景下发挥着重要的作用。