静态工厂方法vs实例(普通)构造函数?

在两种语言都可用的情况下,您是否希望看到实例构造函数或返回实例的静态方法?

例如,如果你从char[]创build一个String

  1. String.FromCharacters(chars);

  2. new String(chars);

在Effective Java第2版中 ,Joshua Bloch当然推荐前者。 有几个我能记住的原因,当然有些我不能:

  • 你可以给这个方法一个有意义的名字。 如果你有两种构造一个实例的方法,它们都是inttypes,但是对于inttypes有不同的含义,使用普通的方法可以使调用代码更具可读性。
  • 第一个推论 – 你可以有不同的工厂方法与相同的参数列表
  • 您可以返回null作为“潜在预期失败”的情况下,而构造函数将始终返回一个值或抛出一个exception
  • 你可以返回一个非声明的types(例如返回一个派生类)
  • 您可以将其用作工厂,以便多次返回对同一对象的引用

缺点:

  • 这并不像现在这样习惯 – 开发者更习惯于看到“新”
  • 如果你看到“新”,你知道你正在得到一个新的实例(模最近我提到的怪异 )
  • 您需要为子类创build合适的构造函数
  • 在C#3中,构造函数调用能够使用对象初始化expression式以紧凑的方式设置字段/属性; 该function不适用于静态方法调用

我写一个构造函数时创build实例没有副作用,即当构造函数正在做的唯一事情是初始化属性。 我写了一个静态方法(并使构造函数是私有的),如果创build实例做了一些你通常不希望构造函数做的事情。

例如:

 public class Foo { private Foo() { } private static List<Foo> FooList = new List<Foo>(); public static Foo CreateFoo() { Foo f = new Foo(); FooList.Add(f); return f; } } 

因为我坚持这个惯例,如果我看到的话

 Foo f = Foo.CreateFoo(); Bar b = new Bar(); 

在阅读我的代码时,对于这两行代码中的每一行都有一个非常不同的期望。 这段代码并没有告诉我创build一个Foo与创build一个Bar不同的是什么,但它告诉我,我需要看看。

如果你的对象是不可变的,你可以使用静态方法返回caching的对象,并保存内存的分配和处理。

ICSE'07有一篇文章研究了构造函数与工厂模式的可用性。 虽然我更喜欢工厂模式,但研究表明,开发人员在find正确的工厂方法时速度较慢。

~NatProg/papers/Ellis2007FactoryUsability.pdf

最近我一直在研究一个公共的API,而且我一直在苦于静态工厂方法和构造方法的select。 静态工厂方法在某些情况下是有意义的,但是在其他情况下,静态工厂方法并不那么清楚,我不确定与API的其他部分的一致性是否足以将其包含在构造函数中。

无论如何,我遇到了一个与Josh Bloch的Bill-Venners访谈的引用 ,我发现它很有帮助:

当你正在写一个类的时候,你可以把我的书的静态工厂的优点列表放到公共构造函数中。 如果你发现这些优势实际上适用于你的情况,那么你应该去静态工厂。 否则,你应该去与构造函数。

有些人对我的书中的build议感到失望。 他们阅读并说:“你们对于静态工厂的争论非常激烈,所以我们应该默认使用它们。” 我认为这样做唯一真正的缺点是,习惯使用构造函数创build对象的人有些不安。 而且我认为它在程序中提供了一点点的视觉提示。 (你没有看到新的关键字。)在文档中find静态工厂也有点困难,因为Javadoc把所有的构造函数分组在一起。 但是我想说,每个人都应该考虑静态工厂,并在适当的时候使用它们。

读过这个引用,以及Uri提到的研究 ,我倾向于错误地赞成build设者,除非有令人信服的理由否则。 我认为没有好的原因的静态工厂方法可能只是不必要的复杂性和过度工程。 虽然明天我可能会改变主意

*不幸的是,这个研究关注的不是静态工厂方法,而是更多地关注工厂模式(其中存在一个单独的工厂对象来创build新实例),所以我不确定是否可以真正得出静态工厂方法混淆了许多程序员的结论。 虽然这项研究给我的印象是他们经常会这样做。

静态方法。 然后你可以返回一个null,而不是抛出一个exception(除非一个引用types)

我更喜欢实例构造函数,只是因为这对我来说更有意义,并且与您要expression的内容(即:如果FromCharacters是采用单个字符的方法会发生什么)相比,潜在的歧义性更小。 当然,主观的。

我个人更喜欢看到一个正常的构造函数,因为构造函数应该被用来构造。 但是,如果有一个很好的理由使用一个,即FromCharacters明确表示它没有分配新的内存,这是值得的。 调用中的“新”有意义。

这取决于。 对于使用实例构造函数的语言是“正常的”,我通常会使用一个,除非我有充分的理由不这样做。 这遵循最less惊喜的原则。

顺便说一句,你忘了另一个常见的情况:空/默认的构造函数与初始化方法配对。

Jon Skeet解释了Josh Bloch的观点,在许多情况下,为什么静态工厂方法比构造函数更可取? 我会说,如果这个类是一个简单的,没有昂贵的设置或复杂的用法,留在惯用的构造函数。 现代JVM使对象创build非常快速和便宜。 如果这个类可能被子类化,或者你可以使它不可变(这对于并发编程来说是一个很大的优势,而这个优势只会变得更重要),那就用工厂方法吧。

还有一个小费。 不要命名工厂方法Foo.new*Foo.create* 。 具有这些名称的方法应该总是返回一个新的实例,这样做会错过工厂方法的一大优点。 更好的命名约定是Foo.of*Foo.for* 。 新的Google Collections Library在这方面做得非常好,imho。