1. 简单工厂(Simple Factory)

在下面这段代码中,我们根据配置文件的后缀(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,返回jsonreturn "json";}
}

在“规范和重构”那一部分中,我们有讲到,为了让代码逻辑更加清晰,可读性更好,我们要善于将功能独立的代码块封装成函数。按照这个设计思路,我们可以将代码中涉及parser 创建的部分逻辑剥离出来,抽象成createParser() 函数。重构之后的代码如下所示:

public class RuleConfigSource {public RuleConfig load(String ruleConfigFilePath) {String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);IRuleConfigParser parse = createParse(ruleConfigFileExtension);if(parse==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,返回jsonreturn "json";}public IRuleConfigParser createParse(String configFormat){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();}return parser;}
}

为了让类的职责更加单一、代码更加清晰,我们还可以进一步将 createParser() 函数剥离到一个独立的类中,让这个类只负责对象的创建。而这个类就是我们现在要讲的简单工厂模式类。具体的代码如下所示:

public class RuleConfigSource {public RuleConfig load(String ruleConfigFilePath) {String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);IRuleConfigParser parse = RuleConfigSource.createParse(ruleConfigFileExtension);if(parse==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,返回jsonreturn "json";}
}
public class RuleConfigParserFactory {public static IRuleConfigParser createParse(String configFormat){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();}return parser;}
}

为了让类的职责更加单一、代码更加清晰,我们还可以进一步将 createParser() 函数剥离到一个独立的类中,让这个类只负责对象的创建。而这个类就是我们现在要讲的简单工厂模式类。具体的代码如下所示:大部分工厂类都是以“Factory”这个单词结尾的,但也不是必须的,比如 Java 中的DateFormat、Calender。除此之外,工厂类中创建对象的方法一般都是 create 开头,比如代码中的 createParser(),但有的也命名为 getInstance()、createInstance()、newInstance(),有的甚至命名为 valueOf()(比如 Java String 类的 valueOf() 函数)等
等,这个我们根据具体的场景和习惯来命名就好。

在上面的代码实现中,我们每次调用 RuleConfigParserFactory 的 createParser() 的时候,都要创建一个新的 parser。实际上,如果 parser 可以复用,为了节省内存和对象创建的时间,我们可以将 parser 事先创建好缓存起来。当调用 createParser() 函数的时候,我们从缓存中取出 parser 对象直接使用。

这有点类似单例模式和简单工厂模式的结合,具体的代码实现如下所示。在接下来的讲解中,我们把上一种实现方法叫作简单工厂模式的第一种实现方法,把下面这种实现方法叫作简单工厂模式的第二种实现方法。

public class RuleConfigParserFactory { private static final HashMap<String,RuleConfigParser> cachedParsers= new HashMap<String, RuleConfigParser>();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()) {//返回null还是IllegalArgumentException全凭你自己说了算return null;}IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());return parser;}
}

对于上面两种简单工厂模式的实现方法,如果我们要添加新的 parser,那势必要改动到RuleConfigParserFactory 的代码,那这是不是违反开闭原则呢?实际上,如果不是需要频繁地添加新的 parser,只是偶尔修改一下 RuleConfigParserFactory 代码,稍微不符合开闭原则,也是完全可以接受的。

除此之外,在 RuleConfigParserFactory 的第一种代码实现中,有一组 if 分支判断逻辑,是不是应该用多态或其他设计模式来替代呢?实际上,如果 if 分支并不是很多,代码中有if 分支也是完全可以接受的。应用多态或设计模式来替代 if 分支判断逻辑,也并不是没有任何缺点的,它虽然提高了代码的扩展性,更加符合开闭原则,但也增加了类的个数,牺牲了代码的可读性。

总结一下,尽管简单工厂模式的代码实现中,有多处 if 分支判断逻辑,违背开闭原则,但权衡扩展性和可读性,这样的代码实现在大多数情况下(比如,不需要频繁地添加parser,也没有太多的 parser)是没有问题的。

2. 工厂方法(Factory Method)

如果我们非得要将 if 分支逻辑去掉,那该怎么办呢?比较经典处理方法就是利用多态。按照多态的实现思路,对上面的代码进行重构。重构之后的代码如下所示:

public interface IRuleConfigParserFactory {IRuleConfigParser createParser();
}
public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory{@Overridepublic IRuleConfigParser createParser() {return new JsonRuleConfigParser();}
}
public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory {@Overridepublic IRuleConfigParser createParser() {return new XmlRuleConfigParser();}
}
public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory {@Overridepublic IRuleConfigParser createParser() {return new YamlRuleConfigParser();}
}
public class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactory {@Overridepublic IRuleConfigParser createParser() {return new PropertiesRuleConfigParser();}
}

实际上,这就是工厂方法模式的典型代码实现。这样当我们新增一种 parser 的时候,只需要新增一个实现了 IRuleConfigParserFactory 接口的 Factory 类即可。所以,工厂方法模式比起简单工厂模式更加符合开闭原则。

从上面的工厂方法的实现来看,一切都很完美,但是实际上存在挺大的问题。问题存在于这些工厂类的使用上。接下来,我们看一下,如何用这些工厂类来实现 RuleConfigSource的 load() 函数。具体的代码如下所示:

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 support");}IRuleConfigParser parser = parserFactory.createParser();String configText = "";//从ruleConfigFilePath文件中读取配置文本到configText中RuleConfig ruleConfig = parser.parse(configText);return ruleConfig;}private String getFileExtension(String filePath) {//...解析文件名获取扩展名,比如rule.json,返回jsonreturn "json";}
}

从上面的代码实现来看,工厂类对象的创建逻辑又耦合进了 load() 函数中,跟我们最初的代码版本非常相似,引入工厂方法非但没有解决问题,反倒让设计变得更加复杂了。那怎么来解决这个问题呢?

我们可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象。这段话听起来有点绕,我把代码实现出来了,你一看就能明白了。其中,RuleConfigParserFactoryMap 类是创建工厂对象的工厂类,getParserFactory() 返回的是缓存好的单例工厂对象。

public class RuleConfigParserFactoryMap {private static final HashMap<String,IRuleConfigParserFactory> cachedFactories= new HashMap<String, IRuleConfigParserFactory>();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 类和 parserfactory 类,并且在 RuleConfigParserFactoryMap 类中,将新的 parser factory 对象添加到 cachedFactories 中即可。代码的改动非常少,基本上符合开闭原则。

实际上,对于规则配置文件解析这个应用场景来说,工厂模式需要额外创建诸多 Factory类,也会增加代码的复杂性,而且,每个 Factory 类只是做简单的 new 操作,功能非常单薄(只有一行代码),也没必要设计成独立的类,所以,在这个应用场景下,简单工厂模式简单好用,比工方法厂模式更加合适。

3. 总结

当创建逻辑比较复杂,是一个“大工程”的时候,我们就考虑使用工厂模式,封装对象的创建过程,将对象的创建和使用相分离。何为创建逻辑比较复杂呢?我总结了下面两种情况。

  • 第一种情况:类似规则配置解析的例子,代码中存在 if-else 分支判断,动态地根据不同的类型创建不同的对象。针对这种情况,我们就考虑使用工厂模式,将这一大坨 if-else创建对象的代码抽离出来,放到工厂类中。

  • 还有一种情况,尽管我们不需要根据不同的类型创建不同的对象,但是,单个对象本身的创建过程比较复杂,比如前面提到的要组合其他类对象,做各种初始化操作。在这种情况下,我们也可以考虑使用工厂模式,将对象的创建过程封装到工厂类中

对于第一种情况,当每个对象的创建逻辑都比较简单的时候,我推荐使用简单工厂模式,将多个对象的创建逻辑放到一个工厂类中。当每个对象的创建逻辑都比较复杂的时候,为了避免设计一个过于庞大的简单工厂类,我推荐使用工厂方法模式,将创建逻辑拆分得更细,每个对象的创建逻辑独立到各自的工厂类中。

同理,对于第二种情况,因为单个对象本身的创建逻辑就比较复杂,所以,我建议使用工厂方法模式。
除了刚刚提到的这几种情况之外,如果创建对象的逻辑并不复杂,那我们就直接通过 new来创建对象就可以了,不需要使用工厂模式。

现在,我们上升一个思维层面来看工厂模式,它的作用无外乎下面这四个。这也是判断要不
要使用工厂模式的最本质的参考标准。

  • 封装变化:创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。
  • 代码复用:创建代码抽离到独立的工厂类之后可以复用。
  • 隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象。
  • 控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁。

王争 | 设计模式之美 - 工厂模式:我为什么说没事不要随便用工厂模式创建对象?相关推荐

  1. 王争 | 设计模式之美 - 职责链模式

    文章目录 1. 职责链模式的定义 2. 职责链模式的代码实现方式1 1. 处理器抽象类 Handler 2. 具体处理器 HandlerA 和 HandlerB 3. 处理器链 HandlerChai ...

  2. 《设计模式》--学习王争设计模式课总结

    #设计模式学习 ##原则 单一职责 开闭原则:对扩展开放,修改关闭 里斯替换:用来指导继承关系中子类该如何设计,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑及不破坏原有程序的正确性. 接口隔 ...

  3. java为什么用工厂模式_【Java】为什么建议没事不要随便用工厂模式创建对象?...

    一般情况下,工厂模式分为三种更加细分的类型:简单工厂.工厂方法和抽象工厂.在这三种细分的工厂模式中,简单工厂.工厂方法原理比较简单,在实际的项目中也比较常用.而抽象工厂的原理稍微复杂点,在实际的项目中 ...

  4. 【送书福利】谷歌工程师王争重磅新书《设计模式之美》

    关注我们丨文末赠书 所谓练武不练功,到老一场空,以技术为驱动的程序员同样如此.面向对象编程范式.设计原则.代码规范.重构技巧和设计模式这些程序员基本功在根本上决定了这个职业的发展高度.把这些基本功练好 ...

  5. 设计模式之美-王争-极客时间-返现24元 限时优惠

    极客时间出品的<设计模式之美>由王争所作,王争是前Google工程师手把手教你写高质量代码 前Google工程师,<数据结构与算法之美>专栏作者.本专栏前Google工程师手把 ...

  6. 最新Java设计模式之美

    目录: ┣━━不定期加餐 (3讲) ┃    ┣━━加餐一丨用一篇文章带你了解专栏中用到的所有Java语法.html ┃    ┣━━加餐一丨用一篇文章带你了解专栏中用到的所有Java语法.m4a ┃ ...

  7. 本人亲自整理的极客时间设计模式之美下部的硬核笔记(残缺版)最近加班太多,搞不了太多,只能尽量了xd们

    设计模式之美(下)https://www.yuque.com/zcming123/uygxde/cbwnad 这位猿,三连,再走吧! 以下内容是为了让搜索引擎,检测到这篇文章.要阅读体验,请点击上面的 ...

  8. 极客时间-设计模式之美 王争 听课笔记

    文章目录 极客时间-设计模式之美 王争 01 每个程序员都要尽早学习并掌握设计模式相关知识 02 哪些维度评判代码质量? 03 面向对象.设计原则.设计模式.编程规范.重构,这五者有何关系? 04 当 ...

  9. 03-04 | 设计模式之美——王争

    以心法为基础,以武器运用招式应对复杂的编程问题. 文章目录 03 | 面向对象.设计原则.设计模式.编程规范.重构,这五者有何关系? 写在前面: 1 . 面向对象 2 . 设计原则 3 . 设计模式 ...

最新文章

  1. 图解C# Console 输出和Console相关编程复习总结
  2. JVM命令查看与设置参数
  3. python制作购物网站_Python实现的购物车功能示例
  4. netflix_Netflix的Polynote
  5. java句柄数过高怎么解决_主播个人及企业利润高,个税或企业所得税怎么解决...
  6. 小积累-生成固定位数的随机数
  7. 未来通信设备的体系构架
  8. Oracle DB_LINK如何使用
  9. 广东省惠州市谷歌卫星地图下载
  10. 2019年中科院JCR分区表公布!附完整Excel下载地址
  11. gbase oracle mysql_项目从Oracle数据迁移到GBase数据库时解决适配遇到的问题
  12. java子窗口获取父窗口句柄_java获得窗口句柄
  13. 【数学建模】CUMCM-2009B 眼科病床的合理安排 解题思路整理
  14. visio如何改变折线箭头拐弯方向
  15. Android 多语言对照表
  16. 7-6 厘米换算英尺英寸 (15 分)
  17. 度秘语音引擎app_语音引擎下载
  18. Cannot connect:由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。192.168.0.113:22
  19. 数据库中的视图理解和优点介绍
  20. 前端 JS实现彩票开奖走势图 连线

热门文章

  1. iOS开发中,出现错误:Apple Mach-O Linker Error
  2. (干货分享)中国最优秀的散文100篇
  3. 美团小程序怎么弄 饿了么cps推广 外卖cps小程序源码 饿了么cps平台 外卖领券小程序源码 美团cps分销源码免费领取 外卖红包小程序cps 饿了么cps分销免费源码 饿了么外卖分佣小程序 ——
  4. 学习web前端,需要哪些技术呢?
  5. 深入理解PHP之设置类的属性
  6. 易优cms小程序插件优化版上线
  7. 三羟基苯-1,3,5-三甲醛 cas34374-88-4 2,5-二羟基对苯二甲醛 cas1951-36-6 中间体|单体材料
  8. Microsoft Edge浏览器使用体验
  9. What is GSLB
  10. 我也来写款12306购票软件