Как я могу выполнить инъекцию зависимостей без нарушения инкапсуляции?
Использование Пример внедрения зависимостей из Википедии:
public Car {
public float getSpeed();
}
Примечание. Другие методы и свойства (например, PushBrake(), PushGas(), SetWheelPosition()) опущено для ясность
Это хорошо работает; вы не знаете, как мой объект реализует getSpeed
- он "инкапсулирован".
В действительности мой объект реализует getSpeed
как:
public Car {
private m_speed;
public float getSpeed( return m_speed; );
}
И все хорошо. Кто-то конструирует мой объект Car
, мигает педаль, рупор, рулевое колесо, и автомобиль отвечает.
Теперь скажем, я изменяю внутреннюю детализацию реализации своего автомобиля:
public Car {
private Engine m_engine;
private float m_currentGearRatio;
public float getSpeed( return m_engine.getRpm*m_currentGearRatio; );
}
Все хорошо. Car
выполняет правильные OO-принципы, скрывая детали того, как что-то делается. Это освобождает вызывающего абонента для решения его проблем, а не пытается понять, как работает автомобиль. Это также дает мне свободу изменить мою реализацию, поскольку я считаю нужным.
Но инъекция зависимостей заставит меня подвергнуть мой класс объекту Engine
, который я не создал или не инициализировал. Еще хуже то, что я теперь обнаружил, что мой Car
даже имеет движок:
public Car {
public constructor(Engine engine);
public float getSpeed();
}
И теперь внешнее слово знает, что я использую Engine
. Я не всегда использовал движок, я не хочу использовать Engine
в будущем, но я больше не могу изменить свою внутреннюю реализацию:
public Car {
private Gps m_gps;
public float getSpeed( return m_gps.CurrentVelocity.Speed; )
}
не нарушая вызывающего абонента:
public Car {
public constructor(Gps gps);
public float getSpeed();
}
Но инъекция зависимости открывает целую банку червей: открывая всю банку червей. Инъекция зависимостей требует, чтобы все детали личной реализации моих объектов были открыты. Потребитель моего класса Car
теперь должен понимать и обрабатывать все ранее скрытые внутренние тонкости моего класса:
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();
}
Как я могу использовать достоинства Injection Dependency, чтобы помочь модульному тестированию, не нарушая достоинства инкапсуляции, чтобы помочь юзабилити?
См. также
- Должна ли зависимость влечения за счет инкапсуляции? (обязательно, а не как)
Ради удовольствия я могу обрезать пример getSpeed
только тем, что необходимо:
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);
}
}
Обновление: Возможно, какой-то ответ может следовать примеру вопроса и дать пример кода?
public Car {
public float getSpeed();
}
Другой пример: мой класс зависит от другого объекта:
public Car {
private float m_speed;
}
В этом случае float
- это класс, который используется для представления значения с плавающей запятой. Из того, что я читаю, каждый зависимый класс следует вводить - в случае, если я хочу издеваться над классом float
. Это поднимает призрак необходимости вводить каждый частный член, поскольку все в принципе является объектом:
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,
...
}
Это действительно детали реализации, которые я не хочу, чтобы клиент должен был смотреть.