1.概述
工厂模式是一种创建型模式,主要作用就是创建对象,将对象的创建过程和使用的过程进行解耦。我们平时说的工厂模式实际上是对三种不同类型的工厂模式的统称,简单工厂、工厂方法、抽象工厂,而在23种设计模式中,只定义了工厂方法和抽象工厂,将简单工厂看作是工厂方法的一种特例,本篇主要讲述的是简单工厂。
简单工厂,就像它的名字一样突出一个简单,就是将业务流程代码中直接使用new
关键字来创建对象,修改为通过一个工厂类
创建对象,这就是简单工厂。如果仅仅只是将new
操作转移到了一个新的类里面,看起来只是在徒增类的数量和代码量,并没有什么意义。
那为什么我们还要使用简单工厂模式呢?
是因为它在某些特定的场景下有其存在的价值,让我们从不同的场景来看看。
2.从不同场景看简单工厂的意义
2.1.框架或工具的封装
除了日常业务开发之外,有时候我们也可能得做一些框架或者工具的开发,这部分开发出来的工具需要提供给其他的开发人员使用,而他们在使用的时候,第一步就是获取到这个工具的对象,这种情况下就可以给使用者提供一个简单工厂,让使用者通过工厂来创建对象。
例如在Java
中使用日历相关的工具Calendar
就是通过简单工厂提供的获取日历对象的方法,如下图:
我们做类似的框架或工具开发的时候,完全可以参照Calendar
的方式,给使用者提供创建对象的能力,从而让使用者无需关心对象的实际创建过程,而是只需要通过特定的方法和参数,就能获取到一个可供使用的对象。
2.2.复杂业务对象的创建
即使是在做业务相关开发,有时候也会涉及到一些相对复杂的业务对象的创建(例如DDD中的领域对象),这时候可以使用简单工厂将产品对象的创建流程从业务流程中抽离。由于创建对象的复杂性被隔离在工厂类中,因此当涉及到产品类的变化时,比如增加新功能、改变实现方式等,只需要改动工厂类,不会对使用产品的其他模块造成影响,有利于产品创建逻辑的集中管理,以及系统的维护和版本升级。
之前在做一个项目开发的时候使用过简单工厂处理过Domain
对象的创建,先说一下背景:
这个项目使用的时COLA架构(一种DDD的代码层面实践的框架),在这个架构的分层中Domain
层属于最底层,如下图中的demoWeb-domain
:
Domain
层中会完成大部分的业务逻辑,但我们知道业务流程中往往伴随着与中间件、其他服务之间的交互(例如数据库的存取操作),但这部分交互不应该由领域对象来实现。
在COLA
中的做法就是提供一个“防腐层”来实现,所谓的防腐层就是在domain
层中定义与中间件交互的interface
再交由infrastructure
来实现,这样在domain
层就只需要关心自己需要做一个什么交互,而不需要关心具体的交互实现,如下的红框所示。
背景介绍完了,说一下这里面存在一个问题,就是Domain
对象为了保证业务模型的纯洁性,一般不会使用Spring来管理领域对象的生命周期,在这种情况下如何才能将gatewayImpl
对象注入到领域对象中呢?
答案是在Infrastructure
层中,将getewayImpl
对象set到领取对象中,如果领域对象的创建过程相对复杂,就可以使用简单工厂进行创建,统一管理创建逻辑,代码如下:
@Component
public class MsgWecomAppFactory {@Resourceprivate MsgWecomGateway msgWecomGateway;@Resourceprivate MsgWecomCacheGateway msgWecomCacheGateway;@Resourceprivate 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;}
}
2.3.与其他模式的组合使用
策略模式中的选择器实现:
在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.总结
本篇主要讲述了工厂模式中的特例:简单工厂模式,并通过3种不同的场景来介绍这种模式存在的意义,有以下几方面:
- 封装对象创建过程: 将对象的创建过程封装工厂类中,使用者无需了解具体产品的创建细节,只需调用工厂类提供的静态方法即可得到所需的产品对象。这样可以隐藏产品类的具体实现,降低耦合度。
- 控制逻辑集中: 将复杂对象的创建对象逻辑集中在工厂类中,如果需要修改或扩展产品类型时,只需要修改工厂类中的代码。这使得添加新产品或者调整产品创建逻辑更加方便、集中管理,也有利于系统的维护和版本升级
- 组合其他模式满足特定的需求: 在某些应用场景中,如根据参数动态选择不同类型的对象实例化,简单工厂+单例提供简洁有效的解决方案,避免了直接使用 new 关键字创建对象带来的硬编码问题。
最后,虽然简单工厂模式在一定程度上提高了灵活性和可维护性,但它也有其局限性,例如违反了开闭原则,每增加一个新产品就需要修改工厂类的代码。
但瑕不掩瑜,简单工厂模式在很多简单场景下发挥着重要的作用。