How can i perform dependency injection without breaking encapsulation?
Using a Dependency Injection example from Wikipedia:
public Car {
public float getSpeed();
}
Note: Other methods and properties (e.g. PushBrake(), PushGas(), SetWheelPosition() ) omitted for clarity
This works well; you don't know how my object implements getSpeed
- it is "encapsulated".
In reality my object implements getSpeed
as:
public Car {
private m_speed;
public float getSpeed( return m_speed; );
}
And all is well. Someone constructs my Car
object, mashes pedals, the horn, the steering wheel, and the car responds.
Now lets say i change an internal implementation detail of my car:
public Car {
private Engine m_engine;
private float m_currentGearRatio;
public float getSpeed( return m_engine.getRpm*m_currentGearRatio; );
}
All is well. The Car
is following proper OO-principles, hiding details of how something is done. This frees the caller to solve his problems, rather than trying to understand how a car works. It also gives me the freedom to change my implementation as i see fit.
But dependency injection would force me to expose my class to an Engine
object that i didn't create or initialize. Even worse is that I've now exposed that my Car
even has an engine:
public Car {
public constructor(Engine engine);
public float getSpeed();
}
And now the outside word is aware that i use an Engine
. I didn't always use an engine, i may want to not use an Engine
in the future, but i can no longer change my internal implementation:
public Car {
private Gps m_gps;
public float getSpeed( return m_gps.CurrentVelocity.Speed; )
}
without breaking the caller:
public Car {
public constructor(Gps gps);
public float getSpeed();
}
But dependency injection opens a whole can of worms: by opening the whole can of worms. Dependency Injection requires that all my objects private implementation details be exposed. The consumer of my Car
class now has to understand, and deal with, all of the previously hidden internal intricacies of my class:
public Car {
public constructor(
Gps gps,
Engine engine,
Transmission transmission,
Tire frontLeftTire, Tire frontRightTire, Tire rearLeftTire, Tire rearRightTire,
Seat driversSeat, Seat passengersSeat, Seat rearBenchSeat,
SeatbeltPretensioner seatBeltPretensioner,
Alternator alternator,
Distributor distributor,
Chime chime,
ECM computer,
TireMonitoringSystem tireMonitor
);
public float getSpeed();
}
How can i use the virtues of Dependency Injection to help unit testing, while not breaking the virtues of encapsulation to help usability?
See also
- Must Dependency Injection come at the expense of Encapsulation? (Must, rather than how)
For the sake of fun, i can trim down the getSpeed
example to just what is needed:
public Car {
public constructor(
Engine engine,
Transmission transmission,
Tire frontLeftTire, Tire frontRightTire
TireMonitoringSystem tireMonitor,
UnitConverter unitsConverter
);
public float getSpeed()
{
float tireRpm = m_engine.CurrentRpm *
m_transmission.GetGearRatio( m_transmission.CurrentGear);
float effectiveTireRadius =
(
(m_frontLeftTire.RimSize + m_frontLeftTire.TireHeight / 25.4)
+
(m_frontRightTire.RimSize + m_frontRightTire.TireHeight / 25.4)
) / 2.0;
//account for over/under inflated tires
effectiveTireRadius = effectiveTireRadius *
((m_tireMonitor.FrontLeftInflation + m_tireMontitor.FrontRightInflation) / 2.0);
//speed in inches/minute
float speed = tireRpm * effetiveTireRadius * 2 * Math.pi;
//convert to mph
return m_UnitConverter.InchesPerMinuteToMilesPerHour(speed);
}
}
Update: Perhaps some answer can follow the question's lead, and give sample code?
public Car {
public float getSpeed();
}
Another example is when my class depends on another object:
public Car {
private float m_speed;
}
In this case float
is a class that is used to represent a floating-point value. From what i read, every dependant class should be injected - in case i want to mock the float
class. This raises the spectre of having to inject every private member, since everything is fundamentally an object:
public Car {
public Constructor(
float speed,
float weight,
float wheelBase,
float width,
float length,
float height,
float headRoom,
float legRoom,
DateTime manufactureDate,
DateTime designDate,
DateTime carStarted,
DateTime runningTime,
Gps gps,
Engine engine,
Transmission transmission,
Tire frontLeftTire, Tire frontRightTire, Tire rearLeftTire, Tire rearRightTire,
Seat driversSeat, Seat passengersSeat, Seat rearBenchSeat,
SeatbeltPretensioner seatBeltPretensioner,
Alternator alternator,
Distributor distributor,
Chime chime,
ECM computer,
TireMonitoringSystem tireMonitor,
...
}
These really are implementation details that i don't want the customer to have to look at.