我有两个对象的“血统”:
Car
是SportsCar
Engine
是SportsEngine
Car
个字段之一是engine
,类型为Engine
。 SportsCar
会覆盖此字段,而是具有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()
。所以:
答案 0 :(得分:1)
在C#中,在构造函数中调用虚方法本身并不错。如果您没有访问非初始化字段,一切都很好。与C ++的方法相反,在构造函数中调用虚方法将调用实际对象的方法,而不是基础方法。 (有关详细信息,请查看these articles。)
我认为虚拟功能的方法没有任何问题;也许你需要一个特定于运动车的发动机作为运动发动机。
关于使用SportsCar声明SportsEngine字段的旧方法还有一个注意事项:新字段不会替换旧字段:您无法覆盖字段!所以你最终得到一辆有两个引擎的汽车,这有点奇怪。只要SportsEngine
是Engine
,您就应该重复使用现有字段。
答案 1 :(得分: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
,正如克里斯的答案中所指出的那样,你可以建造汽车。
答案 2 :(得分:0)
当构造子类时,如果超类(派生子类的类)是具体类(不是抽象类),则子类构造函数将调用超类的构造函数以创建超类字段和方法作为创建子类的一部分。
在你的情况下,当你执行engine = new SportsEngine();
时,因为类引擎是一个具体类,作为构造SportEngine的派生类的一部分,也会调用引擎的构造函数。
我建议您将引擎设置为抽象类(tutorial on C# abstract classes),然后从引擎抽象类GenericEngine和SportsEngine派生。您可以使用可变类型的引擎来保存这些派生类中的任何一个。
您可能也希望从Delegate design pattern开始阅读本文。