《设计模式之美》工厂模式(上):我为什么说没事不要 随便用工厂模式创建对象?

王争《设计模式之美》学习笔记

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

简单工厂(Simple Factory)

文中举例

  • 在下面这段代码中,我们根据配置文件的后缀(json、xml、yaml、properties),选择不同的解析器(JsonRuleConfigParser、XmlRuleConfigParser……),将存储在文件中的配置解析成内存对象RuleConfig。
  • 类 RuleConfigSource
  • 方法 private String getFileExtension(String filePath) 解析文件名获取扩展名。
  • 方法 public RuleConfig load(StringruleConfigFilePath) 中调用 getFileExtension(ruleConfigFilePath),然后根据获取的扩展名写一堆 ifelse 选择解析器 parser,最后输出 RuleConfig。

重构一

  • 将方法 public RuleConfig load(StringruleConfigFilePath) 中选择解析器 parser 的代码提取出来,生成方法 private IRuleConfigParser createParser(String configFormat)。

重构二(简单工厂模式一)

  • 将“重构一”中生成的方法 private IRuleConfigParser createParser(String configFormat) 进一步剥离到一个新的类 RuleConfigParserFactory 中,让类的职责更加单一、代码更加清晰。
  • 以上这个类直负责对象的创建,这个类就是我们现在要讲的简单工厂模式类。
  • 大部分工厂类都是以“Factory”这个单词结尾的,但也不是必须的。工厂类中
    创建对象的方法一般都是create开头,这个我们根据具体的场景和习惯来命名就好。

重构三(简单工厂模式二)

  • 在上面的代码实现中,我们每次调用 RuleConfigParserFactory 的 createParser() 时候,都要创建一个新的 parser。实际上,如果 parser 可以复用,为了节省内存和对象创建的时间,我们可以将 parser 事先创建好缓存起来。当调用 createParser() 函数的时候,我们从缓存中取出 parser 对象直接使用。这有点类似单例模式和简单工厂模式的结合。
  • 改造类 RuleConfigParserFactory,把 cachedParsers 放在一个 static 的 map 中,这样在 createParser 方法中 parser 直接从 cachedParsers 取出即可。

总结

  • 在以上两种简单工厂模式中,如果我们要添加新的 parser,那势必要改动到 RuleConfigParserFactory 的代码,如果不是需要频繁地添加新的 parser,稍微不符合开闭原则,也是完全可以接受的。
  • 在简单工厂模式一中,有一组 ifelse 分支判断逻辑,如果用多态或其他设计模式替代,虽然提高了代码的扩展性,但也增加了类的个数,牺牲了代码的可读性。

工厂方法(Factory Method)

  • 以上的例子,如果我们非要将 if 分支逻辑去掉,比较经典处理方法就是利用多态。
  • 先定义一个接口 IRuleConfigParserFactory。
  • 然后再定义四个不同格式的实现类,比如 JsonRuleConfigParserFactory 等,实现接口 IRuleConfigParserFactory。
  • 这样当我们新增一种 parser 的时候,只需要新增一个实现了 IRuleConfigParserFactory 接口的 Factory 类即可。
  • 工厂方法模式比起简单工厂模式更加符合开闭原则。

调用问题

  • 以上工厂方法看似完美,实际存在挺大问题,在调用这些工厂类时候。
  • public RuleConfig load(String ruleConfigFilePath) 中要写一堆 ifelse 来判断调用哪个工厂实现类。这是将厂类对象的创建逻辑又耦合进了 load() 函数中,这又跟我们最初实现的简单工厂模式一十分相似。

重构

  • 我们可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象。
  • 我们可以利用上面的简单工厂模式二与现在完成的工厂模式相结合,创建类 RuleConfigParserFactoryMap。
  • 把 cachedParsers 还是放在一个 static 的 map 中,这样在 createParser 方法中 parser 直接从 cachedParsers 取出即可。改造点在于 map 中我们 cachedFactories 时是分别调用不同的工厂实现类 JsonRuleConfigParserFactory 等。
  • 当我们需要添加新的规则配置解析器的时候,我们只需要创建新的 parser 类和 parser factory类,并且在 RuleConfigParserFactoryMap 类中,将新的 parser factory对象添加到 cachedFactories 中即可。

总结

  • 实际上,对于规则配置文件解析这个应用场景来说,工厂模式需要额外创建诸多 Factory 类,也会增加代码的复杂性,而且,每个 Factory 类只是做简单的 new 操作,功能非常单薄(只有一行代码),也没必要设计成独立的类,所以,在这个应用场景下,简单工厂模式简单好用,比工厂方法模式更加合适。
  • 当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。
  • 如果对象不可复用,那工厂类每次都要返回不同的对象。如果我们使用简单工厂模式来实现,就只能选择第一种包含 if 分支逻辑的实现方式。如果我们还想避免烦人的 if-else 分支逻辑,这个时候,我们就推荐使用工厂方法模式。

抽象工厂(Abstract Factory)

  • 在以上例子中,如果类有两种分类方式,比如,我们既可以按照配置文件格式来分类,也可以按照解析的对象(Rule 规则配置还是 System 系统配置)来分类,那就会对应下面这8个 parser 类:
    • 针对规则配置的解析器:基于接口 IRuleConfigParser
      • JsonRuleConfigParser
      • XmlRuleConfigParser
      • YamlRuleConfigParser
      • PropertiesRuleConfigParser
    • 针对系统配置的解析器:基于接口ISystemConfigParser
      • JsonSystemConfigParser
      • XmlSystemConfigParser
      • YamlSystemConfigParser
      • PropertiesSystemConfigParser
  • 针对这种特殊的场景,如果还是继续用工厂方法来实现的话,我们要针对每个 parser 都编写一个工厂类,也就是要编写8个工厂类。抽象工厂就是针对这种非常特殊的场景而诞生的。我们可以让一个工厂负责创建多个不同类型的对象(IRuleConfigParser、ISystemConfigParser等),而不是只创建一种 parser 对象。这样就可以有效地减少工厂类的个数。
  • 创建接口 IConfigParserFactory,里面定义两种分类方式创建 parser,比如 createRuleParser() 和 createSystemParser()。
  • 然后写四种工厂实现类实现此接口,比如 public class JsonConfigParserFactory implements IConfigParserFactory,实现类里面都要实现接口中的两个方法。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值