Dot-Net

我是否正確理解 DI/IoC?

  • August 31, 2012

我目前正在嘗試了解使用 IoC 容器的好處並熟悉 DI。我已經開始使用 StructureMap,因為它看起來相當簡單但功能強大。

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

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 是一個“高級”模組,它不應該依賴於任何較低級別的實現細節,它應該只引用這些類並能夠要求它們做他們的事情而不必知道在做什麼,消費程式碼應該負責創建這些預配置的模組並將其交給它。那正確嗎?因此,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...
}

加上所有的新聞來設置它,這真是令人作嘔;即使我創建了變數,它仍然很醜。使用 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...
}

這更清潔,更易於維護,如果我正在編寫單元測試,我只需將具體類替換為 Mock 即可。簡而言之,它的好處是它把所有東西都解耦到我什至不需要關心的地方(在呼叫程式碼中,即)一個特定的類依賴什麼,我可以使用容器創建一個並讓它做確保消費程式碼只知道它需要什麼——例如,在一個真實的應用程序中,如果控制器正在呼叫服務,它不需要知道儲存庫、計算器或規範,它只需要知道使用OrderService 對訂單做一些事情。

這個理解正確嗎?在那張紙條上,有幾件事我還不確定:

  1. 如果您決定使用 IoC 容器,它是在應用程序中的任何地方使用,僅在您需要解決許多反向依賴項的地方,還是僅在消費者中使用?例如,在 OrderRepository 中,如果我使用具體的實現並新建一個 Order;此類是否也會使用 StructureMap 來獲取訂單?這可能是一個有點愚蠢的問題,但我看到的所有 DI/IoC 範例都只關注在消費客戶端(例如網頁)中使用它,而從未涉及在其他地方使用它。這似乎是一種全有或全無的方法:如果您要使用 IoC 容器,那麼它會在任何地方使用;new SomeObject();在這種情況下,您實際上是將任何呼叫替換為ObjectFactory.GetInstance<ISomeObject>();
  2. 無論是否需要使用 DI/IoC 或類似模擬它,讓每個類(當然,在可能的情況下)都從介面派生是好還是壞?我見過很多程式碼範例,其中每個不是內置類的類背後都有一個介面,雖然我可以看到這樣做的好處和可能的未來證明,但我認為遵循 TDD 或 BDD 可能是一個這裡的因素是使用這些方法通常會告訴您是否需要類的介面,但我見過並與許多人交談過,無論是否使用 TDD,都認為您永遠不應該將對象的類型定義為具體的類;它應該總是是作為底層類型的介面或抽像類。這似乎是“不必要的複雜性”程式碼氣味的一個案例,更不用說違反 YAGNI 了。

您的兩個問題都涉及有爭議的話題,但我會站在辯論的一邊。

如果您決定使用 IoC 容器,它是在應用程序中的任何地方使用,僅在您需要解決許多反向依賴項的地方,還是僅在消費者中使用?

您的頂級應用程序(消費者)是唯一應該了解您的依賴注入框架的組件。您不需要在new整個程式碼庫中進行替換,因為每個對像都應該具有完成其工作所需的所有依賴實例(Miško Hevery 的“依賴注入神話:引用傳遞”最終讓我明白了這一點)。

無論是否需要使用 DI/IoC 或類似模擬它,讓每個類(當然,在可能的情況下)都從介面派生是好還是壞?

這個問題的其餘部分錶明您已經知道這個問題的答案:只建構介面以創建更合適的抽象(比所討論的具體類)或提供一些其他值。

引用自:https://stackoverflow.com/questions/4608221