本篇Blog继续学习创建型模式,创建型模式的主要关注点是怎样创建对象,它的主要特点是将对象的创建与使用分离,这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。本篇学习的是简单工厂模式。由于学习的都是设计模式,所有系列文章都遵循如下的目录:
- 模式档案:包含模式的定义、模式的特点、解决什么问题、优缺点、使用场景等
- 模式结构:包含模式的结构,包含的角色定义及调用关系
- 模式实现:包含模式的实现方式代码举例或者生活中简单问题映射代码举例
- 模式实践:如果工作中或开源项目用到了该模式,就将使用过程贴到这里,并且客观讨论使用的是否恰当
- 模式对比:如果模式相似或模式有额外的替换方法,有必要体现其相似点及不同点,区分使用,说明哪些场景下使用哪种模式比较好
- 模式扩展:如果模式有与标准结构定义不同的变体形式,一并体现出其变体结构;对模式的思考需要进行发散等。
接下来所有设计模式的介绍都暂且遵循此基本行文逻辑吗,如果某一条目没有则无需体现,但条目顺序遵循此结构
模式档案
在日常开发中,凡是需要生成复杂对象的地方,都可以尝试考虑使用工厂模式来代替。复杂对象指的是类的构造函数参数过多等对类的构造有影响的情况,因为类的构造过于复杂,如果直接在其他业务类内使用,则两者的耦合过重,后续业务更改,就需要在任何引用该类的源代码内进行更改,光是查找所有依赖就很消耗时间了,更别说要一个一个修改了
一般情况下,工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂。不过,在 GoF 的《设计模式》一书中,它将简单工厂模式看作是工厂方法模式的一种特例,所以工厂模式只被分成了工厂方法和抽象工厂两类。实际上,前面一种分类方法更加常见,所以我们沿用第一种分类方法,在后续的两篇Blog中逐一对工厂方法模式和抽象工厂模式进行学习
模式定义:简单工厂模式是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在简单工厂模式中,创建对象不会对调用者暴露创建逻辑,而是通过使用接口来指向新创建的对象。
模式特点:简单工厂模式的主要特点是:通过静态工厂方法,调用者不需要知道创建细节,只需要知道自己想要的对象名称即可获取该对象。
解决什么问题:在面向对象编程中,最常用的创建对象的方法是通过new操作符构造一个对象实例。但是在一些情况下, new操作符直接生成对象会带来一些问题。举例来说,许多类型对象的创造需要一系列的步骤: 可能需要计算获取对象的初始设置; 可能需要选择创建哪个对象的子对象实例; 可能在创建需要的对象前必须先创建一些辅助功能的对象。 在这些情况对象的创建就是一个 【过程】,不仅是一个【操作】,像一部大机器中的一个个齿轮传动,使调用者能轻松方便地构造对象实例,而不必关心构造对象实例的细节和复杂过程
优点: 一个调用者想创建一个对象,只要知道其名称就可以了,细节不必关注;
缺点:每次增加一个产品时,都需要在静态工厂类中增加一段处理逻辑,不符合开闭原则。
使用场景: 当一种产品实现有多种不同的呈现方式或表现形式时。例如:日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 设计一个连接服务器的框架,需要三个协议,“POP3”、“IMAP”、“HTTP”,可以把这三个作为产品类,共同实现一个接口
模式结构
简单工厂模式又称为静态工厂方法模式。从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。它的特点就是:有一个抽象产品类和多个具体的产品类,只有一个具体的工厂类,一个具体的工厂类可以创建多个具体产品类的实例 先来看看它的组成:
- 具体工厂角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,用来创建产品
- 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。
- 具体产品角色:工厂类所创建的对象就是此角色的实例。在Java中由一个具体类实现。
静态工厂方法模式结构如下
模式实现
我们来看依据上边的工厂方法结构图各类角色如何配合实现:
抽象产品角色
//抽象产品 public interface Product { void show(); }
具体产品角色
//具体产品:ProductA class ConcreteProduct1 implements Product { public void show() { System.out.println("具体产品1显示..."); } } //具体产品:ProductB class ConcreteProduct2 implements Product { public void show() { System.out.println("具体产品2显示..."); } }
具体工厂角色
实际上具体工厂角色有好几种实现方式,例如静态方法形式、单例形式、反射+枚举形式。
1 静态方法形式
实际上,简单工厂模式还叫作静态工厂方法模式(Static Factory Method Pattern)。之所以叫静态工厂方法模式,是因为其中创建对象的方法是静态的:
public class ProductFactory { public static Product getProduct(String kind) { switch (kind) { case "product1": return new ConcreteProduct1(); case "product2": return new ConcreteProduct2(); } return null; } }
之所以设计成静态方法基于如下原因考虑:
- 首先静态的使用更方便,可以不需要通过创建工厂类对象来调用创建对象的方法;
- 第二点这种工具类没有其他面向对象的属性,只负责创建对象,也不需要严格通过对象来进行使用;
- 第三点如果要创建工厂类的对象要么就会发生频繁创建和销毁工厂对象,要么就需要引入单例模式来实现资源节约,而用静态方法可以实现和单例一样的效果
2 单例设计形式
我们也可以依据单例的变体多例的形式实现
class ProductFactory { private static final ConcurrentHashMap<String, Product> map = new ConcurrentHashMap(); static { map.put("product1", new ConcreteProduct1()); map.put("product2", new ConcreteProduct2()); } public static Product getProduct(String productName) { return map.get(productName); } }
3 反射枚举形式
public enum ProductEnum { ConcreteProduct1 ("com.example.factorymethods.ConcreteProduct1 "), ConcreteProduct2 ("com.example.factorymethods.ConcreteProduct2 "), private String className; ProductEnum (String className) { this.className = className; } public String getClassName() { return className; } } public class ProductFactory { public static Product getProduct(ProductEnum productEnum ){ Product product= null; String className = productEnum .getClassName(); try { Class clazz = Class.forName(className); product= (Product)clazz.newInstance(); } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { e.printStackTrace(); } return product; } }
客户端调用如下:
public class FactoryPatternDemo { public static void main(String[] args) { ProductFactory productFactory = new ProductFactory (); //获取产品A ConcreteProduct1 product1= productFactory.getProduct("product1"); //获取产品B ConcreteProduct2 product2= productFactory.getProduct("product2"); product1.show(); product2.show(); } }
打印结果如下:
具体产品1显示... 具体产品2显示...
模式实践
接下来我们看两个简单工厂的模式实践:文件解析工厂、组织处理工厂
设计一个文件解析工厂
我们依据思考模式线性向前,从没有工厂,到简单工厂再到工厂方法。非工厂方法模式就是不使用工厂模式,它的特点就是:只有多个具体的产品类,没有工厂类。我们先来看看不使用工厂方法模式来创建对象是怎样的过程,看如下的一个例子:我们根据配置文件的后缀(json、xml、yaml、properties)
,选择不同的解析器(JsonRuleConfigParser、XmlRuleConfigParser……)
,将存储在文件中的配置解析成内存对象 RuleConfig
public class RuleConfigSource { public RuleConfig load(String ruleConfigFilePath) { String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); IRuleConfigParser parser = null; if ("json".equalsIgnoreCase(ruleConfigFileExtension)) { parser = new JsonRuleConfigParser(); } else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) { parser = new XmlRuleConfigParser(); } else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) { parser = new YamlRuleConfigParser(); } else if ("properties".equalsIgnoreCase(ruleConfigFileExtension)) { parser = new PropertiesRuleConfigParser(); } else { 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"; } }
可以看到,调用者需要自己去创建不同的对象才能调用对象方法,而这几个Parser对象都有相同的执行方法,只不过执行具体内容不同。这样调用者其实就需要关心对象的创建过程了。
1 封装独立功能处理逻辑
为了让代码逻辑更加清晰,可读性更好,我们要善于将功能独立的代码块封装成函数。按照这个设计思路,我们可以将代码中涉及 parser 创建的部分逻辑剥离出来,抽象成 createParser()
函数。重构之后的代码如下所示:
public RuleConfig load(String ruleConfigFilePath) { String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); IRuleConfigParser parser = 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"; } private 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; } }
2 使用简单工厂模式创建文件解析器
继续上边的例子,为了让类的职责更加单一、代码更加清晰,我们还可以进一步将 createParser() 函数剥离到一个独立的类中,让这个类只负责对象的创建。而这个类就是我们现在要讲的简单工厂模式类。具体的代码如下所示实现方式也比较简单:
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; } }
IRuleConfigParser
为抽象产品,JsonRuleConfigParser、XmlRuleConfigParser、YamlRuleConfigParser、PropertiesRuleConfigParser
为具体产品,RuleConfigParserFactory
就是我们的简单工厂,依据不同的后缀提供不同的实现。
3 单例+简单工厂实现资源复用
我们每次调用 RuleConfigParserFactory
的 createParser()
的时候,都要创建一个新的 parser。实际上,如果 parser 可以复用,为了节省内存和对象创建的时间,我们可以将 parser 事先创建好缓存起来。当调用 createParser() 函数的时候,我们从缓存中取出 parser 对象直接使用可以试着把多例模式和简单工厂结合一下,让工厂只提供一份实例对象:
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; } }
设计一个组织任务处理工厂
依据上述模式进行一些简单的模式实践.在工作中其实经常会遇到这种场景,就拿一个同步组织的实现来说吧。同步组织有统一的通用方法,但是不同的业务实现的时候有不同的实现方式:
抽象产品角色
public interface OrgHandler { void syncOrg(SettleContractInfo settleContractInfo); void createCompanyOrgFromBatch(SaasHrTreeDetail waitingCreateCompanyInfo); void createStoreOrgFromBatch(SaasHrTreeDetail waitingCreateStoreInfo); }
具体产品角色
@Service public class CommercialEstateOrgHandler implements OrgHandler{ @PostConstruct public void registerToFactory() { OrgHandlerFactory.register(BizLineEnum.COMMERCIAL.getCode(), this); } @Override public void syncOrg(SettleContractInfo settleContractInfo) { } @Override public void createCompanyOrgFromBatch(SaasHrTreeDetail waitingCreateCompanyInfo) { } @Override public void createStoreOrgFromBatch(SaasHrTreeDetail waitingCreateStoreInfo) { } }
另一个具体的产品角色:
@Service @Slf4j public class BeiJiaOrgHandler implements OrgHandler { @PostConstruct public void registerToFactory() { OrgHandlerFactory.register(BizLineEnum.TEST.getCode(), this); } @Override public void syncOrg(SettleContractInfo settleContractInfo) { } public void syncOrgBackDoor(SettleContractInfo settleContractInfo) { } }
具体工厂方法
public class OrgHandlerFactory { private static final Map<Integer, OrgHandler> orgHandlerMap = new ConcurrentHashMap<>(); public static OrgHandler getOrgHandler(Integer bizLineCode) { OrgHandler orgHandler = orgHandlerMap.get(bizLineCode); if (Objects.isNull(orgHandler)) { throw new BusinessRuntimeException(ErrorNo.PARAM_ERROR); } return orgHandler; } public static void register(Integer bizLineCode, OrgHandler orgHandler) { orgHandlerMap.put(bizLineCode, orgHandler); } }
可以看到实际使用时我们会用Map来存储type类型分别使用,不使用僵化的case
或者if else
。调用时逻辑如下:
private void synchronizationCompany() { List<SaasHrTreeDetail> waitingConfirmCompanyInfo = saasHrTreeDetailBizService.getWaitingConfirmCompanyInfo(); for (SaasHrTreeDetail saasHrTreeDetail : waitingConfirmCompanyInfo) { try { OrgHandler orgHandler = OrgHandlerFactory.getOrgHandler(saasHrTreeDetail.getBizLineCode()); orgHandler.createCompanyOrgFromBatch(saasHrTreeDetail); } catch (Exception e) { LOGGER.error("syncCompanyOrg failed,saasHrTreeDetail:{}", saasHrTreeDetail, e); } } }
总结一下
当一种产品实现有多种不同的呈现方式或表现形式时,比较适合使用工厂模式,而简单工厂其实能适配我们日常使用的大多数场景,依据我在工作中的体验,工厂模式中几乎只用到了简单工厂。在几种简单工厂的实现中,我觉得多例模式(单例+Map)是最合适的工厂实现模式,它解决了if else
这个灾难性问题,下一篇一起来看看工厂方法模式是怎么工作的吧!