如何在不破坏封装的情况下执行依赖注入?
使用维基百科上的依赖注入示例:
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();
}
现在外部世界知道我使用了一个引擎
。我并非总是使用引擎,将来可能不会使用引擎
,但我不能再改变我的内部实现:
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();
}
我如何使用依赖注入的优点来帮助单元测试,同时不破坏封装的优点以帮助可用性?
另请参阅
- 依赖注入必须以封装为代价吗? (必须是这样,而不是如何)
为了好玩,我可以简化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,
...
}
这些确实是实现细节,我不希望客户必须去查看。
Car
类不会暴露或让调用者知道它的依赖关系。依赖关系是内部实现细节。这意味着Car
构造函数没有参数。在内部,构造函数转而调用受保护的构造函数 - 那个需要所有依赖项的构造函数。换句话说:公共构造函数本质上是一个工厂。它创建所有依赖项,然后将它们传递给另一个内部构造函数。这仍然允许测试,但隐藏了依赖关系。 - Ian Boyd