2017-06-19 102 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