在两者都可用的语言中,您希望看到实例构造函数还是返回实例的静态方法?
例如,如果您String
从 a创建一个char[]
:
String.FromCharacters(chars);
new String(chars);
在两者都可用的语言中,您希望看到实例构造函数还是返回实例的静态方法?
例如,如果您String
从 a创建一个char[]
:
String.FromCharacters(chars);
new String(chars);
在Effective Java, 2nd edition中,Joshua Bloch 肯定推荐前者。我记得有几个原因,毫无疑问有些我不记得了:
缺点:
我在创建实例时编写了一个构造函数,没有副作用,即构造函数唯一要做的事情就是初始化属性。如果创建实例执行您通常不希望构造函数执行的操作,我将编写一个静态方法(并将构造函数设为私有)。
例如:
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();
在阅读我的代码时,我对这两行中的每一行所做的事情有着截然不同的期望。该代码并没有告诉我是什么让创建 Foo 与创建 Bar 不同,但它告诉我我需要查看。
我最近一直在研究一个公共 API,我一直在为静态工厂方法与构造函数的选择而苦恼。静态工厂方法在某些情况下肯定是有意义的,但在其他情况下则不是很清楚,我不确定与 API 其余部分的一致性是否足以将它们包含在构造函数中。
无论如何,我在Bill-Venners 对 Josh Bloch 的采访中看到了一句我觉得很有帮助的名言:
当你在写一个类时,你可以在我的书中列出静态工厂相对于公共构造函数的优势。如果您发现这些优势中的大部分实际上适用于您的情况,那么您应该使用静态工厂。否则,您应该使用构造函数。
有些人对在我的书中找到这个建议感到失望。他们读到它并说:“你强烈支持公共静态工厂,我们应该默认使用它们。” 我认为这样做的唯一真正缺点是,对于习惯于使用构造函数来创建对象的人来说,这有点令人不安。而且我认为它在程序中提供的视觉提示要少一些。(您看不到 new 关键字。)此外,在文档中找到静态工厂有点困难,因为 Javadoc 将所有构造函数组合在一起。但是我想说大家应该一直考虑静态工厂,在合适的时候使用。
阅读了那句话,以及Uri 提到的研究* 后,我感觉倾向于错误地支持构造函数,除非有令人信服的理由不这样做。我认为没有正当理由的静态工厂方法可能只是不必要的复杂性和过度工程。虽然明天我可能会再次改变主意......
*不幸的是,这项研究较少关注静态工厂方法,而更多地关注工厂模式(存在一个单独的工厂对象来创建新实例),所以我不确定是否真的可以得出静态工厂方法让许多程序员感到困惑的结论。尽管这项研究确实给我的印象是他们经常会这样做。
如果您的对象是不可变的,您也许可以使用静态方法返回缓存的对象并为自己节省内存分配和处理。
ICSE'07 有一篇论文研究了构造函数与工厂模式的可用性。虽然我更喜欢工厂模式,但研究表明开发人员在找到正确的工厂方法方面速度较慢。
http://www.cs.cmu.edu/~NatProg/papers/Ellis2007FactoryUsability.pdf
这取决于。对于使用实例构造函数是“正常”的语言,我通常会使用一个,除非我有充分的理由不这样做。这遵循最小意外原则。
顺便说一句,您忘记了另一个常见情况:null/default 构造函数与初始化方法配对。
静态方法。然后你可以返回一个null,而不是抛出一个异常(除非是引用类型)
我更喜欢实例构造函数,只是因为这对我来说更有意义,并且你试图表达的内容的潜在歧义更少(即:如果 FromCharacters 是一个采用单个字符的方法怎么办)。不过,当然是主观的。
我个人更喜欢看普通的构造函数,因为构造函数应该是用来构造的。但是,如果有充分的理由不使用它,即如果 FromCharacters 明确声明它没有分配新内存,那将是值得的。调用中的“新”是有意义的。
正如 Jon Skeet 解释 Josh Bloch 所说,在许多情况下静态工厂方法比构造函数更可取的原因有很多。我会说,如果该类是一个简单的类,没有昂贵的设置或复杂的使用,请使用惯用的构造函数。现代 JVM 使对象创建极其快速和廉价。如果该类可能是子类,或者您能够使其不可变(并发编程的一大优势,这只会变得更加重要),然后使用工厂方法。
还有一个提示。不要命名工厂方法Foo.new*
或Foo.create*
. 具有这些名称的方法应始终返回一个新实例,这样做会错过工厂方法的一大优势。更好的命名约定是Foo.of*
or Foo.for*
。Google Guava 库(以前的Google Collections Library)在这方面做得很好,恕我直言。
当然,静态工厂方法相对于构造函数有几个优点。
getInstance()
在单例中。