I'd like to make a class Car
extendable by allowing injection of a user-made subclass of Engine
.
So one user might want a diesel car:
DieselEngine *de = new DieselEngine;
de->setGlowplugTemperature(1200); // something specific for a Diesel
car->setEngine(de);
car->drive();
While the other wants something else:
FluxCapacitorEngine *fce = new FluxCapacitorEngine;
fce->setDestinationYear(1985);
car->setEngine(fce);
car->drive();
Internally, Car
calls (pure) virtual methods of its instance of Engine
in order to do its business. The issue now is, if at a later time the user wishes to do some more configuring on the engine, he would either have to keep a pointer e.g. of type DieselEngine*
externally in order to access it, or use a dynamic cast:
if (DieselEngine *de = dynamic_cast<DieselEngine*>(car->engine()))
de->setMixRatio(2.1);
I don't find either variants particularily nice. Are there alternatives to achieve this kind of customizability/extendability?
A solution that I find lacking (current state): one could leave the implementation of the engine part inside Car
, and make the user subclass the whole thing like class Delorean: public Car
, so he could directly call the specific methods:
delorean->setDestinationYear(1985); // introduced method with the Delorean class
delorean->drive(); // inherited method of Car
However (and this is where the analogy becomes shaky, bear with me) I want to preserve the option of hot-swapping the engine while the Autobahn
and the InsuranceCompany
hold pointers to the car
. This wouldn't be possible if we subclassed Car
because we can't transform a car
to a delorean
instance without changing its pointer.
Another complication:
The current implementation of my Car
doesn't have this extensible, externally settable engine. Instead it's like in the previous paragraph: all the engine parts are in the Car
implementation in the form how 80% of my users need their engine. The configuration setters of the engine part of the car are directly and easily accessible via the car's public interface.
So if I now switch to the external engine concept, I'll be upsetting 80% of my users who are happy with the default engine, because instead of
car->setSparkVoltage(1000);
they would then need to write
if (DefaultGasolineEngine *dge = dynamic_cast<DefaultGasolineEngine*>(car->engine()))
dge->setSparkVoltage(1000);
ugh. They don't care that it's a DefaultGasolineEngine
, they just want to set their familiar spark voltage.
In Summary: Are there alternatives to achieve this kind of customizability/extendability while maintaining a nice interface for the user to his custom class as well as to the default implementation which will be used by the majority of users?
I feel your pain on this. In my experience, your best option is to add support for a more dynamic way of setting properties: by strings. You can continue to support your existing Engine class interface, but add to it some generic property setters (and getters if you like) that will be implemented by each engine. The same property setters can be exposed by the Car class for convenience, which just call the same on its engine object.
Another added benefit of this approach is that you can easily begin configuring your engines from a configuration file, since you now support loading properties from human readable strings. A simple name / value pair or JSON file can be loaded in and the property setters can be called with the values.
The reason I am returning bool from the setters is so that you can know if the property was recognized. You should obviously do some input validation on the value itself as well. You could return an int error code instead to indicate various types of errors, or use exceptions if you prefer.
It would also be acceptable to put the property name fields (e.g. DieselEngine::PROP_SPARK_VOLTAGE) in a single namespace (e.g. EngineProps::SPARK_VOLTAGE) and then clearly document which engine types support which engine properties. In that way, your example code would look like: