8

我目前正試圖學習使用IoC容器的好處並熟悉DI。我開始使用StructureMap,因爲它看起來很簡單而且功能強大。我是否正確理解DI/IoC?

我想驗證一下我對這些概念的理解是否正確。讓我們假設在應用程序中以下基本的類(中省略了簡潔的細節):

public class OrderService : IOrderService 
{ 
    private IOrderRepository _repository; 
    private ITaxCalculator _taxCalculator; 
    private IShippingCalculator _shippingCalculator; 

    public OrderService(IOrderRepository repository, 
     ITaxCalculator taxCalculator, 
     IShippingCalculator shippingCalculator) 
    { 
     this._repository = repository; 
     this._shippingCalculator = shippingCalculator; 
     this._taxCalculator = taxCalculator; 
    } 

    public IOrder Find(int id) { return this._repository.Find(id); } 
} 

public class OrderRepository : IOrderRepository 
{ 
    public IOrder Find(int id) { // ... } 
} 

public class StandardShippingCalculator : IShippingCalculator 
{ 
    // ... 
} 

public class StandardTaxCalculator : ITaxCalculator 
{ 
    private ITaxSpecification _specification; 

    public StandardTaxCalculator(ITaxSpecification specification) 
    { 
     this._specification = specification; 
    } 
} 

首先,依賴倒置原則規定,因爲OrderService是一個「高級別」模塊它houldn't取決於任何較低級別的實現細節,它應該只有那些類的引用,並且能夠請求他們做他們的事情,而不必知道它正在做什麼,並且消費代碼應該負責創建和處理這些預配置的模塊到它。是對的嗎?因此,DI保持這些類鬆散耦合,以便他們不必知道調用該依賴關係的方法的確切方式,只需調用並執行所需的任何操作 - OrderService不會注意存儲庫是查詢XML,還是使用NHibernate或EF甚至原始數據集;它只知道它可以調用存儲庫,告訴它找到一個ID爲42的訂單,並且存儲庫將知道該做什麼。

這也是我的理解,在這種情況下,一個IoC容器,StructureMap提供了一個好處,不會強迫我們確保我們手動創建所有這些依賴項並將它們傳入。例如,Main方法使用上面的代碼瑣碎的應用程序可能有:

static void Main(string[] args) 
{ 
    IOrderService service = new OrderService(
     new OrderRepository(), new StandardShippingService(), 
     new StandardTaxService(new StandardTaxSpecification())); 

    IOrder order = service.Find(42); 

    // Do something with order... 
} 

這是與所有的新聞來設置它revoltingly難看;即使我創建了變量,它仍然很難看。使用IoC容器的讓我避免了這一切,並在StructureMap的情況下,它會變成:

static void Main(string[] args) 
{ 
    ObjectFactory.Initialize(x => 
    { 
     x.For<IOrderRepository>().Use<OrderRepository>(); 
     x.For<IOrderService>().Use<OrderService>(); 
     x.For<IOrder>().Use<Order>(); 
     x.For<IShippingCalculator>() 
         .Use<StandardShippingCalculator>(); 
     x.For<ITaxCalculator>().Use<StandardTaxCalculator>(); 
     x.For<ITaxSpecification>() 
         .Use<StandardTaxSpecification>(); 
    }); 

    IOrderService service = 
       ObjectFactory.GetInstance<IOrderService>(); 
    IOrder order = service.Find(42); 
    // do stuff with order... 
} 

這是更清潔,更容易維護,並讓我剛剛SUB OUT具體的類,比如說,如果我正在編寫單元測試,那就是模擬。簡而言之,它的好處是它將所有的東西都解耦到了我甚至不需要關心的地方(在調用代碼中,也就是說)一個特定的類依賴於什麼,我可以使用容器創建一個並讓它執行這是事情,並確保消費代碼只知道它需要什麼 - 例如,如果控制器正在調用服務,在真正的應用程序中,它不需要知道存儲庫或計算器或規格,所有它需要知道的是使用OrderService對訂單執行某些操作。

這是否理解正確?關於這一點,有幾件事情我不知道的還:

  1. 如果你決定使用IoC容器,是不是意味着要在應用程序中使用隨處可見,只有當你有很多倒依賴關係來解決,還是僅在消費者?例如,在OrderRepository中,如果我使用具體的實現並創建訂單;這個類是否也會使用StructureMap來獲取訂單?這可能有些愚蠢的問題,但我所見過的所有DI/IoC示例只着重於在消費客戶端(例如網頁)中使用它,並且從不涉及在其他地方使用它。看起來它是一種全有或全無的方法:如果您打算使用IoC容器,那麼它在各處都會使用;你基本上是與更換任何調用new SomeObject();,在這種情況下,ObjectFactory.GetInstance<ISomeObject>();

  2. 是否認爲好或壞有每一個類(如果可能的話,當然)從接口是否有必要使用DI派生/ IoC或類似嘲笑它?我見過很多代碼示例,其中每個不是內置類的類都有一個接口,而我可以看到這樣做的好處和可能的未來證明,我認爲繼TDD或BDD之後可能是因爲使用這些方法學通常會告訴你,如果你需要一個類的接口,但我已經看到許多TDD或不知道的人,他們認爲你永遠不應該把一個對象的類型定義爲一個具體的類;它應該總是是一個接口或抽象類作爲基礎類型。這似乎是「不必要的複雜性」代碼味道的情況,更不用說違反了YAGNI。

+2

好問題 - 雖然韋恩有點多讀 - 我不是說這是消極的方式,但我不知道有多少人會經歷這一切。我會盡力的。 – 2011-01-05 19:17:25

+1

是的,當我打字的時候,我會被帶走,因爲我試圖解釋很多,並給出我正在談論的實例:) – 2011-01-05 19:23:13

+1

沒問題 - 你絕對做了很棒的工作,提供例子等。 – 2011-01-05 19:48:30

回答

4

您的問題都涉及有爭議的話題,但我會在我的辯論中權衡。

如果你決定使用IoC容器, 是不是意味着要在 到處使用的應用程序,只有當你有很多 倒依賴性來解決, 或僅在消費?

您的頂級應用程序(消費者)是應該瞭解您的依賴注入框架的唯一組件。你不需要在你的代碼庫中替換new,因爲每個對象都應該有它需要的所有依賴實例來完成它的工作(MiškoHevery的「Dependency Injection Myth: Reference Passing」就是我最終開發的這個家)。

是它認爲好或壞有 每一個類(其中 當然是可能的)從接口 得出是否有必要使用 DI/IoC的或類似嘲諷呢?

此問題的其餘部分顯示您已經知道這個答案:只創建接口以創建更合適的抽象(比所討論的具體類)或提供其他值。

+0

是的,對於「消費者」,我指的是要創建其他類的頂級應用程序,它需要正確地執行它。 – 2011-01-05 20:30:43

1

我目前正在與Visual Studio 2010/12上的C#/ WinForm中的DI/IoC深入地合作。 我的選擇屬於溫莎城堡,但也是StructureMap,但不重要的是您使用的IoCC。

對於非常詳細的答案,我建議您閱讀Mark Seemann的「.NET中的依賴注入」。即使你不用.NET開發,這本書也是一本好書。

關於你的問題:

  1. 一個DI容器是可以潛在地,無論你將 一樣,但是,這並不意味着你應該使用一個庫。儘管您可以分散容器的使用,使其滲透到大部分類中,但您應該將其集中到應用程序的單個區域。

    這個地方叫做 COMPOSITION ROOT,你應該只在該地方使用DI容器。 應用程序的COMPOSITION ROOT應該位於應用程序的 根目錄下,以便它可以正確組成應用程序。 您不應該嘗試撰寫任何模塊中的課程,因爲該方法會限制您的選擇。應用程序模塊中的所有類都應該使用CONSTRUCTOR 注入(或者在極少數情況下,使用其他模式(如Property注入)),並將其留給COMPOSITION ROOT以組成應用程序的對象圖。任何DI CONTAINER 調用應限於組合ROOT。

    COMPOSITION ROOT可以分佈在幾個類中。 這是預期的 - 重要的是所有類都包含在同一個 模塊中,該模塊最好是應用程序根目錄。

  2. 你不需要在任何地方使用接口。你也可以使用具體的類。當然,接口提供了更高層次的抽象,但是你必須考慮你的項目是否需要。例如,在您的應用程序中使用業務邏輯中的接口是一個好習慣。