关于java:为什么建议没事不要随便用工厂模式创建对象

46次阅读

共计 3921 个字符,预计需要花费 10 分钟才能阅读完成。

个别状况下,工厂模式分为三种更加细分的类型:简略工厂、工厂办法和形象工厂。在这三种细分的工厂模式中,简略工厂、工厂办法原理比较简单,在理论的我的项目中也比拟罕用。而形象工厂的原理略微简单点,在理论的我的项目中绝对也不罕用。所以,咱们明天解说的重点是前两种工厂模式。对于形象工厂,略微理解一下即可。

除此之外,这里重点也不是原理和实现,因为这些都很简略,重点还是搞清楚利用场景:什么时候该用工厂模式?绝对于间接 new 来创建对象,用工厂模式来创立到底有什么益处呢?

简略工厂模式 (Simple Factory)

首先,咱们来看,什么是简略工厂模式。咱们通过一个例子来解释一下。在上面这段代码中,咱们依据配置文件的后缀(json、xml、yaml、properties),抉择不同的解析器(JsonRuleConfigParser、XmlRuleConfigParser……),将存储在文件中的配置解析成内存对象 RuleConfig。

为了让代码逻辑更加清晰,可读性更好,咱们要长于将性能独立的代码块封装成函数。依照这个设计思路,咱们能够将代码中波及 parser 创立的局部逻辑剥离进去,形象成 createParser() 函数。重构之后的代码如下所示:

为了让类的职责更加繁多、代码更加清晰,咱们还能够进一步将 createParser() 函数剥离到一个独立的类中,让这个类只负责对象的创立。而这个类就是咱们当初要讲的简略工厂模式类。具体的代码如下所示:

大部分工厂类都是以“Factory”这个单词结尾的,但也不是必须的,比方 Java 中的 DateFormat、Calender。除此之外,工厂类中创建对象的办法个别都是 create 结尾,比方代码中的 createParser(),但有的也命名为 getInstance()、createInstance()、newInstance(),有的甚至命名为 valueOf()(比方 Java String 类的 valueOf() 函数)等等,这个咱们依据具体的场景和习惯来命名就好。

在下面的代码实现中,咱们每次调用 RuleConfigParserFactory 的 createParser() 的时候,都要创立一个新的 parser。实际上,如果 parser 能够复用,为了节俭内存和对象创立的工夫,咱们能够将 parser 当时创立好缓存起来。当调用 createParser() 函数的时候,咱们从缓存中取出 parser 对象间接应用。

这有点相似单例模式和简略工厂模式的联合,具体的代码实现如下所示。接下来咱们把上一种实现办法叫作简略工厂模式的第一种实现办法,把上面这种实现办法叫作简略工厂模式的第二种实现办法。

对于下面两种简略工厂模式的实现办法,如果咱们要增加新的 parser,那势必要改变到 RuleConfigParserFactory 的代码,那这是不是违反开闭准则呢?实际上,如果不是须要频繁地增加新的 parser,只是偶然批改一下 RuleConfigParserFactory 代码,略微不合乎开闭准则,也是齐全能够承受的。

除此之外,在 RuleConfigParserFactory 的第一种代码实现中,有一组 if 分支判断逻辑,是不是应该用多态或其余设计模式来代替呢?实际上,如果 if 分支并不是很多,代码中有 if 分支也是齐全能够承受的。利用多态或设计模式来代替 if 分支判断逻辑,也并不是没有任何毛病的,它尽管进步了代码的扩展性,更加合乎开闭准则,但也减少了类的个数,就义了代码的可读性。对于这一点,咱们在前面章节中会具体讲到。

总结一下,只管简略工厂模式的代码实现中,有多处 if 分支判断逻辑,违反开闭准则,但衡量扩展性和可读性,这样的代码实现在大多数状况下(比方,不须要频繁地增加 parser,也没有太多的 parser)是没有问题的。

工厂办法 (Factory Method)

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

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

从下面的工厂办法的实现来看,所有都很完满,然而实际上存在挺大的问题。问题存在于这些工厂类的应用上。接下来,咱们看一下,如何用这些工厂类来实现 RuleConfigSource 的 load() 函数。具体的代码如下所示:

从下面的代码实现来看,工厂类对象的创立逻辑又耦合进了 load() 函数中,跟咱们最后的代码版本十分类似,引入工厂办法非但没有解决问题,反倒让设计变得更加简单了。那怎么来解决这个问题呢?

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

当咱们须要增加新的规定配置解析器的时候,咱们只须要创立新的 parser 类和 parser factory 类,并且在 RuleConfigParserFactoryMap 类中,将新的 parser factory 对象增加到 cachedFactories 中即可。代码的改变非常少,基本上合乎开闭准则。

实际上,对于规定配置文件解析这个利用场景来说,工厂模式须要额定创立诸多 Factory 类,也会减少代码的复杂性,而且,每个 Factory 类只是做简略的 new 操作,性能十分薄弱(只有一行代码),也没必要设计成独立的类,所以,在这个利用场景下,简略工厂模式简略好用,比工办法厂模式更加适合。

咱们什么时候该用工厂模式,而非简略模式?

咱们后面提到,之所以将某个代码块剥离进去,独立为函数或者类,起因是这个代码块的逻辑过于简单,剥离之后能让代码更加清晰,更加可读、可保护。然而,如果代码块自身并不简单,就几行代码而已,咱们齐全没必要将它拆分成独自的函数或者类。

基于这个设计思维,当对象的创立逻辑比较复杂,不只是简略的 new 一下就能够,而是要组合其余类对象,做各种初始化操作的时候,咱们举荐应用工厂办法模式,将简单的创立逻辑拆分到多个工厂类中,让每个工厂类都不至于过于简单。而应用简略工厂模式,将所有的创立逻辑都放到一个工厂类中,会导致这个工厂类变得很简单。

除此之外,在某些场景下,如果对象不可复用,那工厂类每次都要返回不同的对象。如果咱们应用简略工厂模式来实现,就只能抉择第一种蕴含 if 分支逻辑的实现形式。如果咱们还想防止烦人的 if-else 分支逻辑,这个时候,咱们就举荐应用工厂办法模式。

形象工厂模式 (Abstract Factory)

讲完了简略工厂、工厂办法,咱们再来看形象工厂模式。形象工厂模式的利用场景比拟非凡,没有前两种罕用,所以不是咱们本节课学习的重点,你简略理解一下就能够了。

在简略工厂和工厂办法中,类只有一种分类形式。比方,在规定配置解析那个例子中,解析器类只会依据配置文件格式(Json、Xml、Yaml……)来分类。然而,如果类有两种分类形式,比方,咱们既能够依照配置文件格式来分类,也能够依照解析的对象(Rule 规定配置还是 System 系统配置)来分类,那就会对应上面这 8 个 parser 类。

针对这种非凡的场景,如果还是持续用工厂办法来实现的话,咱们要针对每个 parser 都编写一个工厂类,也就是要编写 8 个工厂类。如果咱们将来还须要减少针对业务配置的解析器(比方 IBizConfigParser),那就要再对应地减少 4 个工厂类。而咱们晓得,过多的类也会让零碎难保护。这个问题该怎么解决呢?

形象工厂就是针对这种十分非凡的场景而诞生的。咱们能够让一个工厂负责创立多个不同类型的对象(IRuleConfigParser、ISystemConfigParser 等),而不是只创立一种 parser 对象。这样就能够无效地缩小工厂类的个数。具体的代码实现如下所示:

总结

三种工厂模式中,简略工厂和工厂办法比拟罕用,形象工厂的利用场景比拟非凡,所以很少用到,不是咱们探讨的重点。所以,上面我重点对前两种工厂模式的利用场景进行总结。

当创立逻辑比较复杂,是一个“大工程”的时候,咱们就思考应用工厂模式,封装对象的创立过程,将对象的创立和应用相拆散。何为创立逻辑比较复杂呢?我总结了上面两种状况。

对于第一种状况,当每个对象的创立逻辑都比较简单的时候,我举荐应用简略工厂模式,将多个对象的创立逻辑放到一个工厂类中。当每个对象的创立逻辑都比较复杂的时候,为了防止设计一个过于宏大的简略工厂类,我举荐应用工厂办法模式,将创立逻辑拆分得更细,每个对象的创立逻辑独立到各自的工厂类中。同理,对于第二种状况,因为单个对象自身的创立逻辑就比较复杂,所以,我倡议应用工厂办法模式。

除了刚刚提到的这几种状况之外,如果创建对象的逻辑并不简单,那咱们就间接通过 new 来创建对象就能够了,不须要应用工厂模式。

当初,咱们回升一个思维层面来看工厂模式,它的作用无外乎上面这四个。这也是判断要不要应用工厂模式的最实质的参考规范。

  • 封装变动:创立逻辑有可能变动,封装成工厂类之后,创立逻辑的变更对调用者通明。
  • 代码复用:创立代码抽离到独立的工厂类之后能够复用。
  • 隔离复杂性:封装简单的创立逻辑,调用者无需理解如何创建对象。
  • 管制复杂度:将创立代码抽离进去,让本来的函数或类职责更繁多,代码更简洁。

正文完
 0