为什么构造函数是邪恶的?

5

我记得读过一篇关于构造函数是“邪恶”的文章(但不知道在哪里)。作者提到构造函数是方法的特殊情况,但有一些限制(例如不能有返回值)。

构造函数真的邪恶吗?是否最好没有构造函数,而是依靠像Initialize这样的方法,以及成员变量的默认值?

(如果必须针对一种语言进行固定,请具体到C#或Java。)


20
“Constructors are evil”这个说法本身过于宽泛,只能用来博取笑声。你提到的那篇文章中可能有一些重要的背景信息…… - Jon
5
如果没有构造函数,面向对象编程将受到相当大的限制。 - Michael Stum
2
简短回答:不是的。它们并不邪恶。不要相信你所读到的一切。 - qJake
2
这是一个非常好的问题,我个人认为。现在它被关闭了,这是SO今天存在的典型例子。似乎只有可悲的无知新手问题或者非常模糊的用法问题才会在这里得到鼓励:(一个好的主观问题到底有什么问题呢? - csharptest.net
2
@Kirk Woll,我已经阅读了常见问题解答和相关材料。我们就说我在这个问题上持有不同意见吧。抱歉我发表了高度主观的观点。 - csharptest.net
显示剩余5条评论
13个回答

8
那听起来像是Allen Holub。有人可能会争论构造函数只是为了增加网络访问量而“邪恶” :) 它们并不比任何其他语言结构更邪恶。它们有好的和坏的影响。当然,你不能消除它们--没有办法在没有它们的情况下构造对象!
然而,你可以做的,这就是Allen所说的,是限制实际调用它们,并在合适的时候更倾向于使用工厂方法,如你的Initialize。原因很简单:它减少了类之间的耦合,并使得在测试或应用程序演变时更容易替换一个类为另一个类。
想象一下,如果你的应用程序执行以下操作:
DatabaseConnection dc = new OracleDatabaseConnection(connectionString);
dc.query("...");

假设这种情况发生在你的应用程序中的一百个地方。现在,你该如何对执行此操作的任何类进行单元测试?而当你切换到Mysql以节省成本时会发生什么呢?

但是如果你这样做:

DatabaseConnection dc = DatabaseConnectionFactory.get(connectionString);
dc.query("...");

然后,要更新您的应用程序,您只需更改DatabaseConnectionFactory.get()返回的内容,这可以通过配置文件来控制。避免显式使用构造函数可以使您的代码更加灵活。
编辑:我找不到“构造函数”文章,但这是他的extends is evilgetters and setters are evil文章。

嗨,欧内斯特,你能给我们提供一些例子,在哪些情况下使用初始化方法比使用非默认构造函数更明智?... - will824
1
这听起来像是Holub会说的话。这个答案很好地解释了当构造函数变得邪恶时会发生什么。 - erickson
除非找到实际的文章,否则这似乎是最接近实际答案的回复。 - ashes999
我记得那是一篇wikiwikiweb的文章。https://wiki.c2.com/?ConstructorsAreEvil - undefined

3
他们并没有。事实上,有一种称为控制反转的特定模式可以巧妙地利用构造函数来使代码解耦,并使维护更加容易。此外,某些问题只能通过使用非默认构造函数来解决。

2

邪恶?不是的。

调用构造函数确实需要使用 "new",这将使您与特定的实现绑定在一起。工厂和依赖注入允许您在运行时更加动态地处理类型,但它们需要通过接口进行编程。

我认为后者更灵活,但认为构造函数邪恶?这太过分了,就像为每件事都设置接口一样太过分了。


2
构造函数并不是邪恶的,但至少在Java中,通常最好使用静态工厂方法(当然其内部仍然使用构造函数)。
以下是Effective Java第一项的几个引用:考虑使用静态工厂方法而不是构造函数

静态工厂方法的优点之一是,与构造函数不同,它们有名称。如果构造函数的参数本身并不能描述返回的对象,那么具有良好选择名称的静态工厂更易于使用,客户端代码也更易于阅读。

...

静态工厂方法的第二个优点是,与构造函数不同,它们不需要每次被调用时创建一个新对象。这使得不可变类(第15条)可以使用预构建的实例,或者在构建时缓存实例,并重复分发它们以避免创建不必要的重复对象。

...

静态工厂方法的第三个优点是,与构造函数不同,它们可以返回其返回类型的任何子类型的对象。

...

静态工厂方法的第四个优点是它们减少了创建参数化类型实例时的冗长。不幸的是,即使从上下文中很明显,当您调用参数化类的构造函数时,仍必须指定类型参数。这通常要求您在短时间内快速提供两次类型参数。
Map<String, List<String>> m =
new HashMap<String, List<String>>();

...

提供仅静态工厂方法的主要缺点是,没有公共或受保护构造函数的类无法被子类化。

...

静态工厂方法的第二个缺点是它们与其他静态方法不容易区分。

我认为构造函数的“问题”在于许多语言将派生类构造函数中构造函数的可访问性绑定到其在派生类中的可访问性。在某些情况下,定义一个仅用于创建T类型实例(而不是派生类型)或仅用于创建派生类型实例(但不是该类)的T类型构造函数会很有帮助。您同意这个观点吗? - supercat
@supercat 听起来不错。就我所了解的Scala,它们似乎实现了一些这样的功能。 - Sean Patrick Floyd

1

构造函数允许初始化列表和其他有用的功能。如果没有复制构造函数,就没有办法在数组中动态初始化对象(不使用对象指针)。

它们并不邪恶。

它们是特殊情况。


1

构造函数并不是邪恶的。它们存在的目的是在类的实例初始化时运行代码。就像任何其他编程概念一样,如果它们没有正确使用,它们可能会成为一个灾难性的工作。但是,如果正确使用,它们可以成为一个伟大(且必不可少)的工具。

http://en.wikipedia.org/wiki/Constructor_(object-oriented_programming)


0

我不会说构造函数是邪恶的。

构造函数返回一个对你正在实例化的对象的引用,应该用于将对象设置为默认状态。我可以看到使用初始化方法的好处,但没有太多意义。除非你需要在对象分配堆栈空间和初始值之后初始化一些逻辑。


0
构造函数并不邪恶。无论你读过什么文章都是错的,最好还是把链接忘掉吧。

高五!因为我们在同一句子中使用了相同的加粗词。o/ - qJake

0

构造函数既不是邪恶的,也不是善良的。如果正确使用并在正确的上下文中使用,构造函数可以成为非常有用的工具。事实上,在.NET语言(如C#)中,如果您在代码中没有明确声明构造函数,则编译器将为您创建一个没有功能的构造函数。


0

构造函数在遵循常规面向对象编程范例时非常有用。在某些情况下,您可能需要对对象创建的方式施加额外的约束,因此,在某些情况下,使用私有构造函数的工厂模式可能更适合。还有一种哲学/最佳实践认为,对象实例化应该与初始化相同,在这种情况下,除了工厂之外,构造函数是您唯一真正的选择。

(当然,工厂在内部仍然使用构造函数)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接