Ian*_*oyd 32 language-agnostic oop unit-testing encapsulation dependency-injection
如何在不破坏封装的情况下执行依赖注入?
public Car {
public float getSpeed();
}
Run Code Online (Sandbox Code Playgroud)
注意:为清楚起见,省略了其他方法和属性(例如PushBrake(),PushGas(),SetWheelPosition())
这很好用; 你不知道我的对象getSpeed是如何实现的- 它是" 封装的 ".
实际上我的对象实现getSpeed为:
public Car {
private m_speed;
public float getSpeed( return m_speed; );
}
Run Code Online (Sandbox Code Playgroud)
一切都很好.有人建造我的Car物体,捣碎踏板,喇叭,方向盘,汽车响应.
现在让我说我改变了我的汽车的内部实现细节:
public Car {
private Engine m_engine;
private float m_currentGearRatio;
public float getSpeed( return m_engine.getRpm*m_currentGearRatio; );
}
Run Code Online (Sandbox Code Playgroud)
一切都很好.将Car是继二OO正确的原则,隐藏的细节如何采取某些措施.这使得呼叫者可以解决他的问题,而不是试图了解汽车的工作原理.它还让我可以自由地改变我的实现.
但依赖注入会迫使我将我的类暴露给Engine我没有创建或初始化的对象.更糟糕的是,我现在已经暴露了我Car甚至有一个引擎:
public Car {
public constructor(Engine engine);
public float getSpeed();
}
Run Code Online (Sandbox Code Playgroud)
现在,外面的词语意识到我使用的是Engine.我并不总是使用引擎,我可能希望Engine将来不使用它,但我不能再改变我的内部实现:
public Car {
private Gps m_gps;
public float getSpeed( return m_gps.CurrentVelocity.Speed; )
}
Run Code Online (Sandbox Code Playgroud)
不打破来电者:
public Car {
public constructor(Gps gps);
public float getSpeed();
}
Run Code Online (Sandbox Code Playgroud)
但依赖注入打开了一大堆蠕虫:通过打开整个蠕虫.依赖注入要求公开我的所有对象私有实现细节.我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();
}
Run Code Online (Sandbox Code Playgroud)
我如何使用依赖注入的优点来帮助单元测试,同时不破坏封装的优点以帮助实现?
为了好玩,我可以将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);
}
}
Run Code Online (Sandbox Code Playgroud)
更新:也许一些答案可以跟随问题的主导,并提供示例代码?
public Car {
public float getSpeed();
}
Run Code Online (Sandbox Code Playgroud)
另一个例子是当我的类依赖于另一个对象时:
public Car {
private float m_speed;
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,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,
...
}
Run Code Online (Sandbox Code Playgroud)
这些确实是我不希望客户看到的实现细节.
Dav*_*vy8 13
许多其他答案提示它,但我会更明确地说是的,依赖注入的天真实现可以打破封装.
避免这种情况的关键是调用代码不应该直接实例化依赖项(如果它不关心它们). 这可以通过多种方式完成.
最简单的是使用默认构造函数来执行使用默认值进行注入.只要调用代码只使用默认构造函数,就可以在不影响调用代码的情况下更改幕后的依赖关系.
如果您的依赖项本身具有依赖性等,这可能会开始失控.此时,Factory模式可以到位(或者您可以从一开始就使用它,以便调用代码已经在使用工厂).如果您介绍工厂并且不想破坏代码的现有用户,则可以始终从默认构造函数调用工厂.
除此之外,还有使用Inversion of Control.我没有足够多地使用IoC来谈论它,但是这里有很多问题以及在线文章可以解释它比我做得更好.
如果它应该真正封装到调用代码无法知道依赖关系的地方,那么可以选择进行注入(具有依赖参数的构造函数或设置器),internal如果语言支持它,或者将它们设为私有并拥有您的单元如果您的语言支持,测试使用像Reflection这样的东西.如果你的语言既不支持,那么我想可能有一种可能是让调用代码的类实例化一个虚拟类,它只是封装了实际工作的类(我相信这是Facade模式,但我从来没有正确记住这些名称) ):
public Car {
private RealCar _car;
public constructor(){ _car = new RealCar(new Engine) };
public float getSpeed() { return _car.getSpeed(); }
}
Run Code Online (Sandbox Code Playgroud)
如果我正确理解您的问题,那么您正试图阻止任何需要实例化新Car对象的类必须手动注入所有这些依赖项.
我用了几个模式来做到这一点.在使用构造函数链接的语言中,我已经指定了一个默认构造函数,它将具体类型注入到另一个依赖注入构造函数中.我认为这是一种非常标准的手动DI技术.
我使用的另一种允许更松散耦合的方法是创建一个工厂对象,该对象将使用适当的依赖关系配置DI'ed对象.然后我将这个工厂注入任何需要在运行时"新"某些汽车的对象; 这允许您在测试期间注入完全伪造的Car实现.
并且始终存在二传注射方法.该对象的属性具有合理的默认值,可以根据需要替换为test-double.不过,我更喜欢构造函数注入.
编辑以显示代码示例:
interface ICar { float getSpeed(); }
interface ICarFactory { ICar CreateCar(); }
class Car : ICar {
private Engine _engine;
private float _currentGearRatio;
public constructor(Engine engine, float gearRatio){
_engine = engine;
_currentGearRatio = gearRatio;
}
public float getSpeed() { return return _engine.getRpm*_currentGearRatio; }
}
class CarFactory : ICarFactory {
public ICar CreateCar() { ...inject real dependencies... }
}
Run Code Online (Sandbox Code Playgroud)
然后,消费者类只通过界面与它进行交互,完全隐藏任何构造函数.
class CarUser {
private ICarFactory _factory;
public constructor(ICarFactory factory) { ... }
void do_something_with_speed(){
ICar car = _factory.CreateCar();
float speed = car.getSpeed();
//...do something else...
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3403 次 |
| 最近记录: |