构造函数和继承

14

让我们以C#为例

public class Foo
{
    public Foo() { }
    public Foo(int j) { }
}

public class Bar : Foo
{

}

现在,除了构造函数之外,所有Foo的公共成员都可以在Bar中访问。

Bar bb = new Bar(1);
为什么构造函数不可继承?
更新: 我知道我们可以链式调用构造函数,但我想知道为什么上述的构造方法是无效的。我相信一定有一个合理的原因。

为什么构造函数不被继承? - Wim Coenen
10个回答

17
构造函数不可继承,因为这可能会引起奇怪和意外的行为。更具体地说,如果在基类中添加了一个新的构造函数,则所有派生类都会获得该构造函数的实例。在某些情况下,这是不好的,因为也许您的基类指定了对于派生类来说没有意义的参数。
对此,通常给出的示例是,在许多语言中,所有对象的基类(通常称为“Object”)都有一个无参数的构造函数。如果构造函数被继承,这将意味着所有对象都有一个无参数的构造函数,并且没有办法说“我希望实例化此类的人提供参数X、Y和Z,否则他们的代码不应编译。” 对于许多类,重要的是为其正确的功能定义某些参数,并使构造函数不可继承是类作者可以保证始终定义某些参数的方法之一。
编辑以回答评论:Ramesh指出,如果像他所希望的那样,构造函数被继承,他总是可以使用每个派生类中私下声明的构造函数来覆盖基类构造函数。这当然是正确的,但这种策略存在一些后勤问题。它要求派生类的编写者紧密关注基类并添加私有构造函数,如果他们想要阻止继承基类构造函数。不仅对于编写派生类的人来说这是很多工作,而且跨类的这种隐式依赖性正是可能引起奇怪行为的事情。
Ramesh-并不是说您所描述的无法添加到语言中。总的来说,不这样做是因为那种行为可能会使人们感到困惑,并导致大量额外的调试和代码编写。
Quintin Robinson在评论中提供了一些非常有价值的回答,绝对值得一读。

如果您在基类中添加构造函数,您将能够初始化仅存在于该类中的内容,这是一种良好的行为,我不明白为什么您认为这是一件坏事。 - Ramesh
@Simon - 如果是这样,我总是可以像函数一样覆盖我的构造函数。 - Ramesh
1
@Ramesh 在这种情况下,您要求对其进行最少的控制,并且派生对象的适当初始化将不存在。 - Quintin Robinson
1
@Ramesh 我认为这类似于说 Bar bar = new Foo(); 并且假设由于 Bar 继承并暴露了来自 Foo 的继承构造函数,因此 Foo 应具有实例化 Bar 所需的所有知识。至少这是我所理解的,你的描述是否正确? - Quintin Robinson
@Ramesh 此外,请考虑在派生类中覆盖的基类成员,基类构造函数盲目地设置值而不知道更改逻辑的后果。正如大多数其他人所说,这可能会导致发生未知/奇怪的事情。 - Quintin Robinson
显示剩余5条评论

8
他们是通过链接的方式实现的,你需要在派生对象中链接构造函数。例如:

(代码示例)

public class Foo
{
    public Foo() { }
    public Foo(int j) { }
}

public class Bar : Foo
{
    public Bar() : base() { }
    public Bar(int j) : base(j) { }
}

派生对象中的构造函数将链接到基本对象中的构造函数。如果您想进一步阅读,这篇文章提供了更多示例。

我理解链式调用,但我想知道为什么我的例子无效(我已更新问题以凸显这一点)。 - Ramesh
我明白了,很抱歉回答了你没有问的问题,我想我读得有点太远了。 - Quintin Robinson
1
谢谢 Quintin,你的问题让我思考了。另外一个问题中的答案给你点赞。 - Ramesh

2

引入构造函数到一个类中的原因之一是因为在没有特定的“依赖项”时,拥有该类的实例是没有意义的。例如,它可能是一个数据访问类,必须具有与数据库的连接:

public class FooRepository
{
    public FooRepository(IDbConnection connection) { ... }
}

如果基类中的所有公共构造函数都可用,那么您的存储库类的用户将能够使用System.Object的默认构造函数来创建一个无效实例:
var badRepository = new FooRepository();

默认情况下隐藏继承的构造函数意味着您可以强制依赖项而不必担心用户创建“无效”的实例。

+1 - 我理解方法不需要隐藏,但构造函数需要用于初始化的原因。 - Ramesh

2
< p > Foo 构造函数只能知道如何初始化一个 Foo 对象,因此它不应该也知道如何初始化任何可能的子类。 < /p >
public class Bar : Foo
{
public Bar(int i) : base(i) { }
}

构造函数传达的信息是:“嘿,基类,请完成你需要做的工作以使自己处于良好状态,这样我就可以正常地设置自己了。”

2
假设构造函数是可继承的。在许多情况下,它们对于子类没有意义,您如何禁用继承的构造函数?
语言设计者选择使构造函数不可继承,而不是通过引入一个机制来阻止继承,从而使语言变得复杂。

如果子类包含任何构造函数,则不会“继承”任何父级构造函数,否则它们都将被继承(意味着构造函数将自动生成以链接到具有匹配签名的父级构造函数)。任何不想“继承”其所有父级构造函数的类都应该提供至少一个自己的构造函数。如果父级没有无参数构造函数,则对于没有构造函数的类来说,没有其他逻辑意义。 - supercat

1
构造函数出于设计原因是不可继承的。(请注意,这是我所知道的每种面向对象语言中都存在的情况。)简单来说,在许多情况下,您真的不希望基类的相同构造函数可用。有关更完整的解释,请参见此 SO 线程

1

0

我认为你可以这样做:

public class Bar : Foo
{
  public Bar (int i)
    : base (i)
  {
  }
}

我可能有点偏离——但这是大致的想法。


我确实理解链式调用,但我想要了解为什么我的示例无效(我已经更新了问题以突出显示相同的内容)。 - Ramesh

0

简单的答案是,语言不是那样工作的。

但你真正想问的问题是为什么它不是那样工作的 :-) 好吧,这是一个任意的选择,并且它遵循了C++和Java(以及很可能影响C#的许多其他语言)。

可能的原因是编译器只会生成一个不带参数的构造函数,并简单地调用父类,如果你想要更多,就需要自己实现。这是最好的选择,因为很可能你需要做的不仅仅是调用父类构造函数。


0

实际上,这是因为父构造函数无法完全初始化子对象。构造函数在这方面有点个人化。这就是为什么大多数语言不继承构造函数的原因。


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