【问题标题】:Do we ever need to prefer constructors over static factory methods? If so, when?我们是否需要更喜欢构造函数而不是静态工厂方法?如果有,什么时候?
【发布时间】:2012-03-27 00:47:27
【问题描述】:

我一直在阅读 Joshua Bloch 的Effective Java,到目前为止它确实名副其实。第一项为 静态工厂方法 优于 构造函数 提供了一个令人信服的案例。以至于我开始质疑优秀的旧构造函数的有效性:)。

本书的优缺点总结如下:

优点:

  1. 他们有名字!
  2. 我们拥有完全的实例控制(单例、性能等)
  3. 它们可以返回子类型/接口
  4. 编译器可以提供类型推断

缺点:

  1. 不能对私有类进行子类化
  2. 它们不像构造函数那样在文档中脱颖而出

第一个缺点实际上可能是一件好事(如书中所述)。第二个,我认为只是一个小缺点,可以通过即将发布的 java 版本轻松解决(javadoc 等的注释)

看起来,最终工厂方法几乎具有构造函数的所有优点,还有很多优点,而没有真正的缺点!

所以,我的问题基本上分为三个部分:

  1. 默认情况下始终使用静态工厂方法而不是构造函数是一种好习惯吗?
  2. 使用构造函数是否合理?
  3. 为什么面向对象语言不为工厂提供语言级别的支持?

注意:有两个类似的问题:When to use a Constructor and when to use getInstance() method (static factory methods)? 和 Creation of Objects: Constructors or Static Factory Methods。然而,答案要么只是提供上述列表,要么重申我已经知道的静态工厂方法背后的基本原理。

【问题讨论】:

  • 另外,根据this,显然工厂在线程安全方面甚至比构造函数更有优势。
  • 我阅读了所引用的文章,但得出了与作者相反的结论... :-) 我明白所指出的内容,但不同意。也许这与我们每个人在编码时通常会看到的特定用例有关。
  • @BrianKnoblauch,我不是并发方面的专家,所以我会相信你的话,因为这些例子似乎有些做作:)

标签: java design-patterns constructor factory-pattern effective-java


【解决方案1】:

静态工厂最终还是要调用构造函数。您可以将大部分功能移到静态工厂中,但不能避免使用构造函数。

另一方面,对于简单的情况,您可以只使用构造函数而无需静态工厂。

构造函数是设置最终字段的唯一方法,恕我直言,这比非最终字段更可取。

您可以在子类中使用构造函数。您不能将静态工厂用于子类。

如果你有一个好的依赖注入框架来构建组件的依赖,你可能会发现静态工厂并没有增加太多。

【讨论】:

  • 当然,工厂在幕后使用构造函数,这就是我包含第三个问题的原因。工厂可以肯定地使用构造函数设置最终字段。我不明白你提到的子类限制;需要详细说明吗?
  • 好吧,你好像在说第一个缺点;但也许我们可以为此提供“除了”工厂之外的构造函数?
  • @MadChuckle,假设您提供了一个抽象骨架类,其中包含一些需要重写的方法。您只能将其与构造函数一起使用——可以与匿名内部类一起使用,也可以仅与子类一起使用。
  • Hrrrrrm。在 DI 合适的地方,它真的很合适,但在不合适的地方,我几乎总是站在工厂方法一边(除非你特别想鼓励子类化)。我数不清有多少次工厂方法使我免于痛苦和广泛的重构。
  • 虽然我在偏好上更符合@LouisWasserman,但这个答案在某种程度上包括了那里指出的案例。我想我会使用工厂方法,除非它是 1)非常简单 2)可以单独用 DI 解决 3)子类。我选择这个作为答案。谢谢你们!
【解决方案2】:

我喜欢您突出显示的类似问题之一的评论:

(关于如何决定是否应该使用工厂方法而不是构造方法),

“你需要决定你想要完成什么。或者你不希望人们调用你的构造函数(你正在创建一个单例或工厂),或者你不介意(如上面的 NumberFormat , 他们正在初始化一些对象以方便调用者)"

对我来说,这意味着 使用构造函数是合理的,其中不需要工厂方法。不需要工厂,它只会让代码更难理解(“这里返回的是哪个确切的子类?”)

【讨论】:

  • 等等。但部分要点是调用者不应该关心返回的确切子类,您可以更改返回的确切子类,而无需更改调用者。
  • 没错,但是如果这些子类以不同的方式实现该 API,那么在尝试追踪代码中的问题时,您可能需要知道返回的是哪个确切的子类。在这些情况下,我发现工厂方法使事情变得模糊。
  • @Disco3: 从调用者的的角度来看,一个合法的工厂方法应该返回一些其方法会起作用的东西,就像父类一样,但这并不意味着内部行为会必须匹配。例如,如果String 是内部可继承的,那么当一个或多个字符超过256 个时,串联字符串可能会返回一个ConcatenatedString 对象,该对象将保存一组对较小字符串的引用。 ConcatenatedString 的所有方法都会产生与包含原始字符的字符串相同的结果,但是...
  • @Disco3: ...某些操作会快得多。客户端不应该关心它是否得到一个包含对两个 256 个字符的字符串对象的引用的 ConcatenatedString 或一个包含 512 个字符的数组的 ArrayBackedString;两种类型应该具有相同的语义。
【解决方案3】:

我非常同意 Josh Bloch 的观点,他比我做得更好,但这里有几个地方不适合静态工厂:

  • 当依赖注入更明智时; DI 系统(当然是 Guice)倾向于通过它们的构造函数注入东西。特别是当您预计构造函数的参数可能会在将来发生变化时,DI 是合适的。
  • 当您特别打算让人们编写子类,并且您正在为他们提供一个框架供他们实现时。

【讨论】:

  • 好点!虽然 DI 系统也可以通过工厂模式实现,具有一些优势(theserverside.com/news/1363653/…),但我倾向于同意你的观点。
  • 虽然只知道构造函数的 DI 系统将被限制为使用构造函数,并且期望具有特定名称的静态工厂方法将被限制为具有该名称的方法的类,但构造函数是否提供从 DI 的角度来看有什么特别的优势? public 构造函数是否比工厂方法提供任何子类化优势(子类化肯定需要受保护的构造函数)。
【解决方案4】:

构造函数很无聊,你确切地知道在哪里搜索它们,你知道它们的范围,它们在某种程度上是可预测的。他们在那里建造物体本身,而不是做神一样的工作。如果你做了太多其他与构造对象没有太大关系的事情,你就知道你做错了什么。仅仅通过重命名方法是不可能“治愈”的。因此,当您没有顶级开发人员时,他们往往更专注,代码质量“更好”。 (当你有后者​​时,我提到的点无关紧要。)

无聊的东西。让我们做一些更有趣和更具智力挑战性的事情......

仅作记录:我没有忽略问题和其他答案中提到的点,我只是想补充一点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-02
    • 2014-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-20
    • 2018-08-04
    • 2019-04-07
    相关资源
    最近更新 更多