2023年12月05日更新:深入解析工厂模式的分类与实现

Java设计模式中的工厂及抽象工厂模式解析

2023年12月05日更新:深入解析工厂模式的分类与实现

更新于:2023年12月5日,上午9点02分45秒;文章作者:与更优秀的自我相遇。

本文重点阐述了Java设计模式中的工厂与抽象工厂模式,其中工厂模式是创建型设计模式中的一种常用方法。通常,工厂模式可细分为三种类型:简单工厂、工厂方法和抽象工厂。实际上,简单工厂不过是工厂方法的一种特殊形式。有兴趣的读者可以进一步查阅相关资料。

目录

前言

在创建型设计模式中,工厂模式是一种普遍采用的设计策略,它通常包括三个类别:基础型工作、工厂方法以及抽象型工作。

其实简单工厂只是工厂方法的一种特例。

现在,我们将探讨如何实施工厂模式,并区分不同类型的工厂模式。

一、简单工厂

在常规的开发实践中,我们必定遭遇过,需依据不同类别来构建各类别对应的具体类别的业务场景。

因此,在代码中会出现大量if、else的语句,对于那些对代码整洁度有较高要求的同事而言,看到过多的if、else语句可能会感到烦恼,他们可能会尝试寻找方法来消除这些语句。

这时,简单工厂的设计模式就派上用场了。

在假定存在一个特定情境下,我们必须依据配置文件的扩展名,如json、xml、yaml或properties,来挑选相应的解析工具。随后,我们将这些文件中存放的配置信息转换并存储为内存中的RuleConfig对象。

代码如下:

 
public class 规则配置源 {
获取规则配置文件路径后,计算其扩展名,并将结果赋值给变量String ruleConfigFileExtension。
创建了一个解析器对象,该对象由规则配置文件扩展名指定的规则配置解析器工厂生成:IRuleConfigParser parser = RuleConfigParserFactory.buildParserFor(ruleConfigFileExtension);
    if (parser == null) {
抛出无效规则配置异常,
规则配置文件格式不兼容:请检查文件路径“”+ruleConfigFilePath+”“。
    }
    String configText = "";
从ruleConfigFilePath文件中提取配置信息,并将这些信息存储于configText变量中。
通过解析器对配置文本进行处理,成功创建了RuleConfig对象,命名为ruleConfig。
    return ruleConfig;
  }
定义一个名为 getFileExtension 的私有方法,该方法接受一个字符串类型的 filePath 参数,用于获取文件扩展名。
解析文件名后提取其扩展部分,例如从"rule.json"中得出json格式。
    return "json";
  }
}
public class 规则配置解析器工厂类 {
初始化一个名为parser的IRuleConfigParser对象,并将其赋值为null。
若配置格式与"json"相同或完全一致。
初始化了一个新的JsonRuleConfigParser对象,命名为parser。
若配置格式与"xml"相同或一致,则不进行修改。
实例化了一个新的XML规则配置解析器对象,命名为parser。
若配置格式与"yaml"相符,则不进行修改。
初始化了一个新的Yaml规则配置解析器对象,名为parser。
若配置格式与“properties”一词完全一致。
初始化一个PropertiesRuleConfigParser对象,命名为parser。
    }
    return parser;
  }
}

另外一种简单工厂的实现方式如下:

 
public class RuleConfigParserFactory {
  private static final Map cachedParsers = new HashMap<>();
  static {
cachedParsers存储了"json"对应的对象,该对象是一个新的JsonRuleConfigParser实例。
cachedParsers存储了"xml"这一键值,并将一个新的XmlRuleConfigParser对象与之关联。
cachedParsers存储了"yaml"键值对,其值是一个新的YamlRuleConfigParser对象。
cachedParsers存储了"properties",并为其分配了一个新的PropertiesRuleConfigParser实例。
  }
  public static IRuleConfigParser createParser(String configFormat) {
若配置格式为空或不存在,{
你可以自主决定是返回null还是抛出IllegalArgumentException异常。
    }
parser = cachedParsers[configFormat.toLowerCase()],此对象为IRuleConfigParser类型。
    return parser;
  }
}

针对上述两种基础的工厂模式实现方式,若需引入新的解析器,便不得不对 RuleConfigParserFactory 的源码进行修改,这难道不是违背了开闭原则的要求吗?

实际上,若非频繁新增parser,仅需偶尔对RuleConfigParserFactory的代码进行微调,即便稍有违背开闭原则,亦是完全可以容忍的。

尽管在简单工厂模式的代码实现中存在多处涉及if分支的判断逻辑,这些逻辑与开闭原则相悖,然而,在考虑到代码的扩展性和可读性后,这种实现方式在多数情形下(例如,无需频繁增加parser,且parser数量不多)依然能够满足需求。

二、工厂方法(Factory Method)

上述所提及的简单工厂模式存在一个显著的不足,即它并不遵循开闭原则。每当需要添加一个新的parser时,便不得不对Factory类的代码进行相应的修改。

那我们有么有办法可以解决这个问题勒。

这便是工厂方法这一设计模式,它能够借助多态的特性,对传统的简单工厂模式进行优化与改进。

代码如下:

 
定义一个公共接口,该接口名为IRuleConfigParserFactory。
创建一个IRuleConfigParser实例的解析器。
}
本类名为JsonRuleConfigParserFactory,它实现了IRuleConfigParserFactory接口。
  @Override
公开方法createParser()用于生成一个IRuleConfigParser实例。
生成一个新的Json规则配置解析器实例,。
  }
}
本类作为XML规则配置解析器工厂,实现了IRuleConfigParserFactory接口。
  @Override
  public IRuleConfigParser createParser() {
创建一个新的Xml规则配置解析器实例。
  }
}
该类名为YamlRuleConfigParserFactory,实现了IRuleConfigParserFactory接口。
  @Override
  public IRuleConfigParser createParser() {
创建一个新的YamlRuleConfigParser对象。
  }
}
public class 属性规则配置解析器工厂类 实现了 IRuleConfigParserFactory 接口
  @Override
  public IRuleConfigParser createParser() {
创建一个新的PropertiesRuleConfig解析器实例。
  }
}

用工厂方法的方式实现上述代码:

 
public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFilePath) {
    String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
IRuleConfig的解析器工厂实例parserFactory尚未被初始化。
若规则配置文件扩展名与“json”字符串相等或忽略大小写后相同,{
初始化JsonRuleConfigParserFactory对象,命名为parserFactory。
若规则配置文件扩展名与“xml”相同不区分大小写,{
创建了一个新的XML规则配置解析器工厂实例,命名为parserFactory。
若规则配置文件扩展名与"yaml"相同或完全一致。
初始化了一个新的Yaml规则配置解析器工厂实例,命名为parserFactory。
若规则配置文件扩展名与"properties"字符串内容完全一致。
初始化了一个新的PropertiesRuleConfigParserFactory对象,并将其赋值给parserFactory变量。
    } else {
抛出InvalidRuleConfigException异常,提示“规则配置文件格式不支持:”并附带ruleConfigFilePath路径信息;
    }
通过parserFactory创建的实例,我得到了一个IRuleConfigParser类型的解析器,命名为parser。
    String configText = "";
    //从ruleConfigFilePath文件中读取配置文本到configText中
    RuleConfig ruleConfig = parser.parse(configText);
    return ruleConfig;
  }
  private String getFileExtension(String filePath) {
    //...解析文件名获取扩展名,比如rule.json,返回json
    return "json";
  }
}

观察上述代码的执行过程,可以发现创建工厂类对象的逻辑已经与 load() 函数紧密相连,这与我们最初编写的代码版本如出一辙。尽管我们引入了工厂方法,问题并未得到解决,反而使得设计变得更加繁琐。那么,我们该如何着手解决这个问题呢?

我们打算为工厂类增设一个简易的工厂,即所谓的“工厂的工厂”,其作用是生成工厂类实例。这样的表述或许显得有些复杂,不过我已经将相应的代码编写完成,相信您浏览过后便能一目了然。在这段代码中,RuleConfigParserFactoryMap 这个类扮演着生成工厂实例的角色,而它的 getParserFactory() 方法则负责提供预先缓存的单例工厂对象。

 
public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFilePath) {
    String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
通过RuleConfigParserFactoryMap获取的解析工厂实例,用于处理扩展名为ruleConfigFile的配置文件,被赋值给了IRuleConfigParserFactory类型的变量parserFactory。
    if (parserFactory == null) {
      throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);
    }
    IRuleConfigParser parser = parserFactory.createParser();
    String configText = "";
    //从ruleConfigFilePath文件中读取配置文本到configText中
    RuleConfig ruleConfig = parser.parse(configText);
    return ruleConfig;
  }
  private String getFileExtension(String filePath) {
    //...解析文件名获取扩展名,比如rule.json,返回json
    return "json";
  }
}
//因为工厂类只包含方法,不包含成员变量,完全可以复用,
由于无需频繁生成新的工厂类实例,因此采用简单工厂模式的第二种实现方式显得更为适宜。
public class RuleConfigParserFactoryMap, 便是工厂的工厂。
  private static final Map cachedFactories = new HashMap<>();
  static {
cachedFactories存储了一个键为"json"的元素,该元素是一个新创建的JsonRuleConfigParserFactory实例。
cachedFactories存储了一个键为"xml"的元素,该元素是一个新创建的XmlRuleConfigParserFactory实例。
cachedFactories存储了一个键名为"yaml"的元素,该元素是一个新的YamlRuleConfigParserFactory实例。
cachedFactories存储中加入了"properties",其对应的是一个新创建的PropertiesRuleConfigParserFactory实例。
  }
提供公共静态方法,用于获取指定类型的解析器工厂实例,方法名为getParserFactory,并接收一个字符串参数type。
若类型为空或不存在,{
      return null;
    }
parserFactory,即IRuleConfigParserFactory,是通过从cachedFactories中获取type变量小写化后的类型对应的实例来创建的。
    return parserFactory;
  }
}

总结:针对规则配置文件解析这一应用场景,采用工厂模式将导致必须额外构建大量 Factory 类,这不仅提升了代码的复杂度,而且每个 Factory 类仅执行基础的 new 操作,功能极其有限(仅一行代码)。鉴于这些 Factory 类没有必要独立成类,因此,在此场景下,简单工厂模式因其简洁易用,相较于工厂方法模式,显得更为适宜。

三、如何选择使用简单工厂和工厂方式

在对象生成过程较为繁复,并非单纯通过new操作即可完成,而是需要整合其他类别的对象,执行一系列初始化步骤时,我们建议采用工厂方法模式,将这一复杂的生成过程分散至多个工厂类中,确保每个工厂类都不会过于复杂。相对而言,若采用简单工厂模式,将所有创建逻辑集中在一个工厂类中,则可能使该工厂类变得相当复杂。

除此之外,在特定情境中,若目标对象无法重复利用,那么工厂类每次必须生成全新的对象。若采用简单工厂模式来构建,我们只能采纳包含if条件判断的第一种实现方案。若我们希望规避令人烦恼的if-else条件分支,此时,我们便推荐采用工厂方法模式。

四、抽象工厂

在简单工厂模式和工厂方法模式中,类的分类方式是唯一的。以规则配置解析的案例为例,解析器类仅会依据配置文件的格式(如Json、Xml、Yaml等)进行归类。然而,针对类别的划分存在两种不同的方法,一方面,我们可以依据配置文件的格式进行区分,另一方面,还可以根据解析的目标(是Rule规则配置还是System系统配置)来进行分类,如此一来,便会产生以下八个解析器类。

专门用于解析规则配置的解析工具:它是以接口IRuleConfigParser为基础构建的。

对于系统配置的解析器,其开发是建立在接口ISystemConfigParser之上的。

在这种特定情境下,若仍旧采用工厂模式进行操作,那么我们必须为每一个解析器单独创建一个工厂类,换句话说,需要编写多达八个这样的工厂类。

若在将来有必要添置新的业务配置解析器(例如:IBizConfigParser),则相应地需额外构建四个工厂类。然而,众所周知,类别的增多将使得系统维护变得更加复杂。

这个问题该怎么解决呢?

抽象工厂就是针对这种非常特殊的场景而诞生的。

一个工厂可以承担起制作多种不同类型对象的任务,例如IRuleConfigParser和ISystemConfigParser等,而不仅仅是单一类型的parser对象。

这样就可以有效地减少工厂类的个数。

具体的代码实现如下所示:

 
定义一个公共接口,名为IConfigParserFactory。
创建一个IRuleConfigParser实例,名为createRuleParser。
创建一个系统配置解析器对象,名为createSystemParser。
此处可引入新的解析器类型,例如,IBizConfigParser。
}
该类名为JsonConfigParserFactory,它实现了IConfigParserFactory接口。
  @Override
公开提供IRuleConfigParser接口的createRuleParser方法,用于生成规则解析器实例。
    return new JsonRuleConfigParser();
  }
  @Override
公开方法createSystemParser用于生成一个系统配置解析器实例。
创建一个新的Json系统配置解析器实例。
  }
}
本类名为XmlConfigParserFactory,它实现了IConfigParserFactory接口。
  @Override
  public IRuleConfigParser createRuleParser() {
    return new XmlRuleConfigParser();
  }
  @Override
  public ISystemConfigParser createSystemParser() {
生成一个新的Xml系统配置解析器实例,。
  }
}
省略了Yaml配置文件解析器工厂类和属性配置文件解析器工厂类的代码。

总结

在构建逻辑较为繁复,堪称一项“庞大工程”的情况下,我们倾向于采纳工厂模式,以此对对象的生成过程进行封装,并实现对象的生成与运用之间的分离。

何为创建逻辑比较复杂呢?

目前,我们需从更高层次审视工厂模式,其功能主要体现在以下四个方面。这四个方面构成了评估是否采纳工厂模式的核心依据。

这篇关于Java设计模式中工厂与抽象工厂模式的详细解析文章,现在就到这里结束了。如需了解更多关于Java工厂及抽象工厂模式的相关内容,请查阅脚本之家过往的发表文章,或者继续阅读下方的相关文章。期待大家今后能继续给予脚本之家更多的支持!

你可能想看:
« 上一篇
下一篇 »

相关推荐

java中什么是冒泡,冒泡算法

2026年09月06日

27阅读

java中冒号,java双冒号

2026年09月06日

26阅读

java中的->,java中的是什么意思

2026年09月06日

24阅读

java中的super用法,Java中super的说明

2026年09月06日

28阅读

java中反射的应用,java中反射的应用场景

2026年09月06日

26阅读

java中开关的定义,Java关键字有哪些

2026年09月06日

26阅读