2017-06-19 100 views
0

的范围标准库类型考虑以下类型:应对依赖注入

class SomeType 
{ 
    public void Configure() 
    { 
     var windowsService = new ServiceController("msdtc"); 
     windowsService.Start(); 
    } 
} 

至少有三个问题。

  1. 我们隐含地依赖于ServiceController。我们不能单元测试Configure()

  2. 我们有一个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,但我们仍然对新型WindowsService一个new运营商的问题。我试图理解应该在DI容器中注册标准ServiceController还是直接使用它(如上所示)(new)?

container.RegisterType<ServiceController>(); 

而且我不知道我们是否应该尝试测试WindowsService也许会更好重写它是这样的:

class WindowsService : ServiceController, IWindowsService 
{ 
} 

由于WindowsService现在只是继承我们不能在这里测试一切。该类型已经由Microsoft进行过测试。 然而,它打破封装,也许从ISP的SOL I D。因为我们可以投IWindowsServiceWindowsService甚至到ServiceController

处理标准稳定类型的最佳方法是什么? 如果有的话,请转到另一个问题。 在此先感谢。

回答

3
interface ISomeInterface 
{ 
    void Configure(); 
} 

class SomeType : ISomeInterface 
{ 
    public void Configure() 
    { 
     var windowsService = new ServiceController("msdtc"); 
     windowsService.Start(); 
    } 
} 

我会像上面那样做。现在什么都不应该直接取决于SomeType。一切都应该取决于ISomeInterface。这将对ServiceController的依赖限制在一个类中。

运营商new真的不是问题。没有IServiceControllerServiceController实现,所以如果你想使用它,你必须把自己绑定它。通过隐藏它实现一个接口的SomeType,至少你已经限制了多少东西直接依赖它。

+0

好的,从你的观点来看,我不需要在di-container中注册ServiceController,并且可以保留隐式依赖关系,对吗? – Serg046

+0

@ Serg046对。你可以注册ServiceController吗?大概。但是自从'SomeType'总是依赖于它,没有多少意义。 – mason

+0

了解,然后看起来像我需要保留'SomeType'是非常小的,并产生尽可能小的业务逻辑,因为它不能被单元测试。指出并等待一段时间的其他一些点。 – Serg046

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>(); 

现在,当您开始编写代码时,您可能会选择为不同的系统功能(例如,也许你想要一个独立于其他操作系统调用的IServiceControlManager)。这很好,也很常见。我更喜欢所有O/S呼叫的一个大班级,因为我知道那些O/S呼叫不会改变。那么,他们可能会改变,但如果他们这样做,我会遇到更大的问题,无论如何返工将是不可避免的。

+0

但你只是重写我的话。 'OperatingSystem'和我的'WindowsService'(第一个版本)之间的主要区别是什么?它看起来一样。另外为了模拟目的,我们不需要虚拟方法,因为我们可以模拟接口。无论如何谢谢你的时间和观点。 – Serg046

+1

我能说什么?我喜欢你的解决方案。 –

+0

“WindowsService”的第二个版本呢?你见过类似的东西吗?它看起来更适合我,因为我们确实需要使用'new'运算符,没有任何东西在这里进行单元测试。然而,在第一个版本中,我们可以单元测试'WindowsService.Start()',尽管这看起来很奇怪。 – Serg046