Here's an analogy of our concrete problem to demonstrate the issue at hand.
We need to manufacture cars, with either petrol or diesel related parts (the parts can be the engine and the exhaust - e.g. different requirements for exhaust for diesel engines).
Engine
+ engineCapacity: Integer
DieselEngine: Engine
PetrolEngine: Engine
+ octanes (Integer)
We then have the car manufacturer interface:
public interface CarManufacturer {
Engine createEngine(Integer engineCapacity);
Exhaust createExhaust(Engine engine);
}
and its implementors
DieselCarManufacturer
PetrolCarManufacturer
There's the CarManufacturerGateway that orchestrates the car building process, selecting a CarManufacturer from a ManufacturersStore according to some criteria and then building a car via:
final var engine = carManufacturer.createEngine(engineCapacity);
final var exhaust = carManufacturer.createExhaust(engine);
Problem definition
To create the proper exhaust, in the PetrolCarManufacturer, we need to use the petrol engine's octanes number (probably a bad analogy) from within the PetrolCarManufacturer::createExhaust.
But, the CarManufacturer interface accepts an Engine, thus we cannot access the petrol engine octanes to decide upon the exhaust configuration to create.
Solutions
- instanceof + downcasting: I think it's considered a bad practice and can fail during runtime
- using double dispatch somehow?
- using parameterized types on the interface, e.g.
public interface CarManufacturer<T extends Engine>and then each implementor will define the concrete type of engine it makes. This also requires theManufacturersStoreto be parameterized in the same way:ManufacturersStore<T extends Engine>. The latter makes me question this option, as it reads weirdly "a car manufacturer store parameterized on the type of engines it makes".
N.B.: If the analogy fails to demonstrate the issue at hand, I can try to rewrite it with our concrete example.
Enginebase class (with a diesel engine returning a nonsense value like0or throwing an exception)? CouldcreateExhaustask the engine for what type (or types) of exhaust would be appropriate?Engineinterface with a detail that only makes sense for one part of the abstraction (certainly the analogy has failed me here).