How to initialize a field in a base class that should be initialized in a subclass?

My base class Car contains an engine field that cannot be initialized in the base class. I can only initialize it in a subclass, for example, in ElectricCar I can write engine = new ElectricEngine . However, I use the field in the base class. Therefore, I have a field that was used but was not initialized:

 public class Car { protected Engine engine; public void Start() { engine.Start(); // do something else } public void Stop { engine.Stop(); // do something else } public void Diagnose() { engine.Diagnose(); // anotherField.Diagnose(); // oneAnotherField.Diagnose(); } } 

What is the best way to initialize the engine?

Version 1. Field guaranteed initialization, but with many field constructors will look ugly. No mistakes, but ugly.

 public class Car { protected Engine engine; public Car(Engine engine) { this.engine = engine; } public void Start() { engine.Start(); // do something else } public void Stop { engine.Stop(); // do something else } public void Diagnose() { engine.Diagnose(); // anotherField.Diagnose(); // oneAnotherField.Diagnose(); } } public class ElectricCar : Car { public ElectricCar() : base (new ElectricEngine()) { } } 

Version 2. Subclasses should remember the initialization of the field, the presence of such a β€œcontract” with subclasses can lead to errors (uninitialized field).

 public class Car { protected Engine engine; public Car() { } public void Start() { engine.Start(); // do something else } public void Stop { engine.Stop(); // do something else } public void Diagnose() { engine.Diagnose(); // anotherField.Diagnose(); // oneAnotherField.Diagnose(); } } public class ElectricCar : Car { public ElectricCar() { engine = new ElectricEngine(); } } 

Version 3. The field must be initialized. The constructor is clear. But calling the virtual method from the constructor (potentially dangerous, not recommended at all).

 public class Car { protected Engine engine; public Car() { InitializeEngine(); } protected abstract void InitializeEngine(); public void Start() { engine.Start(); // do something else } public void Stop { engine.Stop(); // do something else } public void Diagnose() { engine.Diagnose(); // anotherField.Diagnose(); // oneAnotherField.Diagnose(); } } public class ElectricCar : Car { public ElectricCar() { } protected void override InitializeEngine() { engine = new ElectricEngine(); } } 

Therefore, each version has pros and cons. Which version is better? Or perhaps you can even offer something else.

+6
source share
4 answers

Version 3 is nothing more than a template template template . If your base class cannot provide a reasonable default implementation, but you require each car to have an engine, delegating the creation to the base class is a very suitable and safe solution. I would tweak your initialization a bit to be something like this:

 protected abstract Engine InitializeEngine(); 

Then in your constructor for a car:

 public Car() { engine = InitializeEngine(); } 

This will make the contract very clear. Your subclasses just have to provide an engine, and your base class ensures that the engine variable is assigned after the constructor is called.

+5
source

Another option might be something like:

 public class Car { private Engine engine; //PRIVATE protected Engine MyEngine { //PROTECTED PROPERTY get { if(engine == null) engine = new Engine(); return engine; } } } 

Thus, the caller will be safe, which will always use the initialized element, since it is checked inside the protected property, which it can only access because the field is private .

+2
source

I voted for option 1. You clearly state in the constructor that each Car must have an Engine , BrakingSystem , ECU , etc. You also know that they were created before Car . If you delay their creation until the first access and there is a problem with their creation, then it will be more difficult to handle the exception accordingly.

+1
source

Then use the property instead of the field for Engine , because non-private fields are very difficult to debug.

About Design First, you must declare Car as abstract and use IEngine behavior instead of the Engine class. Then, for any particular car (i.e. the Subclass), you can choose the appropriate type of injection (by constructor, by property, ...).

 public interface IEngine { void Start(); void Stop(); void Diagnose(); } public abstract class Car { protected Car(IEngine engine) { Engine = engine; } protected IEngine Engine {get; set;} public void Start() { engine.Start(); // do something else } public void Stop() { engine.Stop(); // do something else } public void Diagnose() { engine.Diagnose(); // anotherField.Diagnose(); // oneAnotherField.Diagnose(); } } public class ConcreteCar : Car { public ConcreteCar(IEngine engine):base(engine) // injection by constructor { } ... } 

Using:

 Car concreteCar = new ConcreteCar(new ConcreteEngine()); 

EDIT

You can force derived classes to initialize the engine. See the updated example.

0
source

All Articles