【问题标题】:Parallel inheritance: How to handle constructors并行继承:如何处理构造函数
【发布时间】:2012-10-13 12:35:33
【问题描述】:

我有两个对象的“血统”:

  1. CarSportsCar 的基类
  2. EngineSportsEngine 的基类

Car 的字段之一是engine,类型为EngineSportsCar 覆盖了这个,取而代之的是一个字段engine,它的类型是SportsEngine

Car 的构造函数有:

public Car() {
    engine = new Engine();
}

SportsCar 的构造函数有:

public SportsCar() : base() {
    engine = new SportsEngine();
}

我的想法是我有一个汽车类型的层次结构,每辆汽车都有一个合适的引擎(它决定了汽车功能的某些方面)。当我对汽车层次结构的某个点进行子类化时,我可以确定这辆车的发动机是否具有与现有汽车相同的马力、重量、燃料使用等,并使用现有发动机填充engine 字段。如果我决定我要添加的新车应该有一个独特的引擎,那么我可以继承Engine 层次结构来制作我的新引擎,然后我可以覆盖engine 字段并将我的新车的构造函数更改为正确初始化它。

问题是,如果我正确理解 C#,当我创建 B 的新实例时,引擎将被初始化两次。严格来说,它并没有打破目前非常简单的情况。但是,当我调用 new SportsCar() 时,它会首先调用 engine = new Engine();engine = new SportsEngine();

从概念上讲,当一辆跑车被制造出来时,你不会放入通用引擎,立即将其拔出,然后放入运动引擎,我认为代码最终会这样做。

实际上,当第一个结果被丢弃时,为什么要进行两次构造函数调用?

我的解决方案是在基础构造函数中调用virtual void CreateEngine() { engine = new Engine(); },并且永远不要在儿童汽车中调用它。然后每辆车实现自己的override void CreateEngine()。所以:

  1. 这种方法有什么我没有发现的问题吗?
  2. 显然在构造函数中调用虚方法是不好的。我的情况是个例外吗?

【问题讨论】:

    标签: c# inheritance


    【解决方案1】:

    正如 Vlad 所指出的,在构造函数中调用虚函数本身并不坏。另一种选择是通过构造函数注入引擎。一种看待这个问题的方法——汽车本身是制造引擎还是有人把它放在那里?那么你会有:

    public class Car {
        public Engine Engine { get; private set; }
        public Car(Engine engine) {
            Engine = engine;
        }
    }
    public class SportsCar: Car {
        public new SportsEngine Engine { 
            get { return (SportsEngine)base.Engine; } 
        }
        public SportsCar(SportsEngine engine): base(engine) {}
    }
    

    然后,您还可以构建 CarFactory,正如 Chris 的回答中指出的那样,构建您的汽车。

    【讨论】:

    • 作为另一种选择,您可以使用带有无参数构造函数的 SportsCar,而该构造函数又调用 : base(new SportsEngine()) 编辑:但也许类似的东西/这应该留给工厂/构建器模式。跨度>
    • 也许是public new SportsEngine -> public new SportsEngine Engine?
    • @Chris,是的,我会把它留给工厂/建筑商。我已将其包含在答案中。
    • 不,引擎是汽车自己制造的。如果我不必在初始化汽车时记住初始化引擎,我会更喜欢——我一定会忘记这样做,然后花几个小时在NullReferenceException 挠头。
    • Re: Factory - 使用 CarFactory 创建汽车并添加适当的引擎似乎是一个优雅的解决方案,但我的问题是每当我修改汽车类层次结构时,我必须记住去工厂并更新汽车制造商的方法。如果我忘记了,我可能会引入令人困惑的错误。这似乎并不优于我已有的解决方案。
    【解决方案2】:

    在 C# 中,在构造函数中调用虚方法本身还不错。如果您没有访问未初始化的字段,一切都很好。与 C++ 的方法相反,在构造函数中调用虚方法将调用实际对象的方法,而不是基础方法。 (查看thesearticles了解更多详情。)

    我认为使用虚函数的方法没有任何问题;只是也许您需要一个特定于跑车的访问器来作为运动引擎。

    关于让 SportsCar 声明 SportsEngine 字段的旧方法的另一个注意事项:新字段不会替换旧字段:您不能覆盖字段!所以你最终会得到一辆有两个引擎的汽车,这有点奇怪。只要SportsEngineEngine,您就应该重用现有字段。

    【讨论】:

      【解决方案3】:

      在构造子类时,如果超类(派生子类的类)是具体类(不是抽象类),则子类构造函数将调用超类的构造函数以创建超类字段和方法作为创建子类的一部分。

      在您的情况下,当您执行 engine = new SportsEngine(); 时,因为类引擎是作为构造 SportEngine 派生类的一部分的具体类,引擎的构造函数也会被调用。

      我建议您将引擎作为一个抽象类 (tutorial on C# abstract classes),然后从引擎抽象类 GenericEngine 和 SportsEngine 派生。您可以使用可变类型的引擎来保存这些派生类中的任何一个。

      您可能还想从Delegate design pattern 上的这篇文章开始。

      【讨论】:

      • 这对我来说似乎是一个肤浅的解决方案。如果我添加一个名为SuperSportsCar 的类,该类从SportsCar 派生,并带有ImprovedSportsEngine?然后我不能使 SportsCar 抽象,因为无论继承如何,我都需要它的实例。
      猜你喜欢
      • 1970-01-01
      • 2014-04-30
      • 2010-09-18
      • 2013-06-01
      • 2023-04-03
      • 1970-01-01
      相关资源
      最近更新 更多