考虑以下类型:
class SomeType
{
public void Configure()
{
var windowsService = new ServiceController("msdtc");
windowsService.Start();
}
}
至少有三个问题。
我们对ServiceController
有隐含的依赖。
我们无法对Configure()
进行单元测试。
我们有new
运算符,违反了我们的DI策略。
为了解决这个问题,我们可以提取另一种类型并将其输入我们的SomeType
。
interface IWindowsService
{
void Start();
}
class WindowsService : IWindowsService
{
private readonly ServiceController _serviceController;
public WindowsService(string serviceName)
{
_serviceController = new ServiceController(serviceName));
}
public void Start() => _serviceController.Start();
}
class SomeType
{
private readonly IWindowsService _msdtcService;
public SomeType(Func<string, IWindowsService> createServiceCallback) //explicit dependency
{
_msdtcService = createServiceCallback.Invoke("msdtc");
}
public void Configure() => _msdtcService.Start();
}
它解决了问题#1和#2,但我们在新类型new
中仍然有一个WindowsService
运算符。我试着理解我应该在DI容器中注册标准ServiceController
还是直接使用它,如上所示(new
)?
container.RegisterType<ServiceController>();
此外,我不确定我们是否应该尝试测试WindowsService
,或者最好将其重写为:
class WindowsService : ServiceController, IWindowsService
{
}
由于WindowsService
现在只是继承,我们无法在此测试任何内容。该类型已经过Microsoft测试。
然而,它打破了封装,也许来自SOL I D的ISP。我们可以将IWindowsService
投放到WindowsService
甚至ServiceController
。
处理标准稳定类型的最佳方法是什么? 如果有的话,请转介我另一个问题。 提前谢谢。
答案 0 :(得分:3)
interface ISomeInterface
{
void Configure();
}
class SomeType : ISomeInterface
{
public void Configure()
{
var windowsService = new ServiceController("msdtc");
windowsService.Start();
}
}
我会像上面那样做。现在什么都不应该直接依赖于SomeType
。一切都应该取决于ISomeInterface
。这使得ServiceController
的依赖性仅限于一个类。
new
运算符确实不是问题。 IServiceController
实现ServiceController
没有SomeType
,所以如果你想使用它,你必须自己绑定它。通过将其隐藏在实现接口的const char *p="people";
char *q="random";
while(*p)
{
*q=*p;
*p++;
*q++;
}
中,至少你已经限制了有多少东西直接依赖它。
答案 1 :(得分:0)
您正在处理的问题是更大问题的子类型,即在IoC中处理系统级调用的问题。
问题的另一个例子是使用DateTime.Now
。如果你在代码中调用它,则无法将其隔离,如果你想在各种时间场景中进行测试,这就是一个问题。
一种解决方案是取消所有系统级调用,以便您可以替换自己的模拟操作系统进行测试。 Here是DateTime的一个示例。我也将为您的具体问题提供一个示例:
interface IOperatingSystem
{
void StartService(string name);
}
class OperatingSystem : IOperatingSystem
{
public virtual void StartService(string name) {
var windowsService = new ServiceController(name);
windowsService.Start();
}
}
class SomeType : ISomeType
{
private readonly IOperatingSystem _operatingSystem;
public SomeType(IOperatingSystem operatingSystem)
{
_operatingSystem = operatingSystem;
}
public void Configure()
{
_operatingSystem.StartService("msdtc");
}
}
在您的IoC容器中:
container.RegisterType<IOperatingSystem, OperatingSystem>();
container.RegisterType<ISomeType, SomeType>();
现在,您可以通过覆盖操作系统类来隔离所有需要的内容:
class MockOperatingSystem : OperatingSystem
{
public override StartService(string name)
{
//Do something worthy of testing, e.g. return normally or throw an exception
}
}
并注册(在您的单元测试中),如下所示:
container.RegisterType<IOperatingSystem, MockOperatingSystem>();
现在,当您开始对此进行编码时,您可能会选择为不同的系统功能使用不同的接口(例如,您可能需要与其他O / S呼叫分开的IServiceControlManager
)。这很好,也很常见。我更喜欢所有O / S呼叫的一个大班,因为我知道那些O / S呼叫不会改变。嗯,他们可能会改变,但如果他们这样做,我会遇到更大的问题,无论如何返工都是不可避免的。