简体   繁体   中英

Parallel inheritance: How to handle constructors

I have two "lineages" of object:

  1. Car is a base class for SportsCar
  2. Engine is a base class for SportsEngine

One of Car 's fields is engine , of type Engine . SportsCar overrides this and instead has a field engine which is of type SportsEngine .

The constructor for Car has:

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

The constructor for SportsCar has:

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

The idea is that I have a hierarchy of car types, with each car having an appropriate kind of engine (which determines certain aspects of the car's function). When I subclass some point of the car hierarchy, I can decide whether this car's engine has the same horsepower, weight, fuel usage etc. as an already existing car, and use an existing engine to populate the engine field. If I decide that the new car I am adding should have a unique engine, then I can subclass the Engine hierarchy to make my new engine, and then I can override the engine field and change the constructor of my new car to initialize it correctly.

The problem is that if I understand C# correctly, when I create a new instance of B the engine will be initialized twice. Strictly speaking, it doesn't break the current very simple case. However, what will happen is when I call new SportsCar() it will first do engine = new Engine(); and the engine = new SportsEngine(); .

Conceptually, when a sports car is made, you don't put in a generic engine, immediately pull it out, then put in a sports engine, which is what I think the code will end up doing.

Practically, why should two constructor calls be made when the first one's result is discarded anyway?

My solution was to call virtual void CreateEngine() { engine = new Engine(); } virtual void CreateEngine() { engine = new Engine(); } in the base contructor, and never call it in child cars. Each car then implements its own override void CreateEngine() . So:

  1. Are there any problems I'm not seeing with this approach?
  2. Apparently calling virtual methods in constructors is bad. Is my case an exception?

In C#, calling virtual methods in the constructor is not bad per se. If you are not accessing the non-initialized fields, everything is fine. Contrary to C++'s approach, calling the virtual method in constructor will call the actual object's method, not the base one. (Look at these articles for more details.)

I don't see any problem with the approach with virtual function; just maybe you need a sportscar-specific accessor to engine as sportsengine.

One more note about your old approach with having SportsCar declare SportsEngine field: the new field doesn't replace the old one: you cannot override a field! So you would end up with a car having two engines, which is kind of weird. As long as SportsEngine is an Engine , you should just reuse the existing field.

As Vlad pointed out, calling virtual functions in constructor is not bad on its own. Another option is to inject engine through the constructor. One way to look at this - does car itself create the engine or does someone put it there? Then you would have:

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) {}
}

You could then also build a CarFactory as pointed out in Chris's answer to build your cars.

When a subclass is constructed, if the superclass (the class from which the subclass is derived) is a concrete class (not an abstract class) then the subclass constructor will invoke the constructor of the superclass in order to create the superclass fields and methods as part of creating the subclass.

In your case, when you do engine = new SportsEngine(); since the class engine is a concrete class as part of constructing the derived class of SportEngine the constructor for engine is also invoked.

What I suggest is that you have engine be an abstract class ( tutorial on C# abstract classes ) and then derive from the engine abstract class GenericEngine and SportsEngine. You can use a variable type of engine to hold either one of these derived classes.

You might want to start with this article on the Delegate design pattern as well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM