簡體   English   中英

正在注入的構造函數注入和初始化依賴項

[英]Constructor injection and initialization of dependencies being injected

我正在編寫一個簡單的控制台應用程序,負責連接到數據庫,從中選擇特定產品(基於提供的標准)並使用此產品進行一些處理。 我將命令行參數存儲到此類的實例:

public class Arguments
{
   public string ConnectionString { get; set; }
   public int ProductId { get; set; }
   public string ProductName { get; set; }
}

在某些時候,我需要從數據庫中獲取產品。 我正在使用以下存儲庫:

public interface IProductRepository
{
   Product GetById(int productId, string connectionString);
   Product GetByName(string productName, string connectionString);
}

然后,我將存儲庫的實現注入到使用它的類中,例如:

public class ProductProcessor
{
   private readonly IProductRepository productRepository;

   public ProductProcessor(IProductRepository productRepository)
   {
      this.productRepository = productRepository;
   }

   public void Process(Arguments arguments)
   {
      Product productToProcess;

      if (!string.IsNullOrEmpty(arguments.ProductName))
      {
         productToProcess = productRepository.GetByName(arguments.ProductName, arguments.ConnectionString);
      }
      else
      {
         productToProcess = productRepository.GetById(arguments.ProductId, arguments.ConnectionString);
      }

      // ....
   }
}

這是有效的,但我不喜歡的設計是IProductRepository每個方法都有一個connectionString參數。 如果沒有涉及依賴注入,我可能會重寫它如下:

public void Process(Arguments arguments)
{
   Product productToProcess;

   ProductRepository productRepository = new ProductRepository(arguments.ConnectionString);

   if (!string.IsNullOrEmpty(arguments.ProductName))
   {
      productToProcess = productRepository.GetByName(arguments.ProductName);
   }
   else
   {
      productToProcess = productRepository.GetById(arguments.ProductId);
   }

   // ....
}

這使我能夠使用更簡單,更易於使用的界面。 當然,現在ProductRepository沒有無參數構造函數,並且很難與DI容器一起使用。 理想情況下,我希望兩全其美,即使用構造函數中的連接字符串初始化ProductRepository ,並從其方法中刪除連接字符串。 實現這一目標的最佳方法是什么?

我已經考慮過的一些方法:

  • 將一個方法Initialize(string connectionString)添加到基本上用作構造函數的IProductRepository 明顯的缺點是我現在需要在GetByIdGetByName方法中執行任何操作之前檢查是否已調用Initialize
  • 不要使用構造函數注入,而是使用Service Locator模式來實例化ProductRepository 我不太喜歡Service Locator,但這可能只是可能的解決方案。

還有更好的選擇嗎?

編輯 :從答案我看到我應該發布更多的上下文。 我使用Ninject作為我的DI容器。 在我的Program.cs中的Main方法中,我將所有依賴項注冊到容器並實例化作為應用程序入口點的類:

public static void Main(string[] args)
{
    StandardKernel kernel = new StandardKernel();
    kernel.Bind<IArgumentsParser>().To<IArgumentsParser>();
    kernel.Bind<IProductProcessor>().To<ProductProcessor>();
    kernel.Bind<IProductRepository>().To<ProductRepository>();

    MainClass mainClass = kernel.Get<MainClass>();
    mainClass.Start(args);
}

MainClass如下所示:

public class MainClass
{
    private readonly IArgumentsParser argumentsParser;
    private readonly IProductProcessor productProcessor;        

    public MainClass(IArgumentsParser parser, IProductProcessor processor)
    {
        argumentsParser = parser;
        productProcessor = processor;
    }

    public void Start(string[] args)
    {
        Arguments parsedArguments = argumentsParser.Parse(args);
        productProcessor.Process(parsedArguments );
    }
}

這使我能夠在一個地方依賴Ninject並創建整個圖形( Main方法),而應用程序的其余部分對DI和容器一無所知。

如果可能的話,我想保持這種方式。

我同意當前的界面設計是一個漏洞的抽象 ,所以讓我們這樣定義它:

public interface IProductRepository
{
    Product GetById(int productId);
    Product GetByName(string productName);
}

您需要的是一個抽象工廠,可以為您創建IProductRepository的實例。

所以ProductProcessor看起來像這樣:

public class ProductProcessor
{
    private readonly IProductRepositoryFactory productRepositoryFactory;

    public ProductProcessor(IProductRepositoryFactory productRepositoryFactory)
    {
        this.productRepositoryFactory = productRepositoryFactory;
    }

    public void Process(Arguments arguments)
    {
        Product productToProcess;

        var productRepository =
            this.productRepositoryFactory.Create(arguments.ConnectionString);
        if (!string.IsNullOrEmpty(arguments.ProductName))
        {
            productToProcess = productRepository.GetByName(arguments.ProductName);
        }
        else
        {
            productToProcess = productRepository.GetById(arguments.ProductId);
        }

        // ....
    }
}

我不確定為什么你需要對命令行參數進行建模? 您應該最小化每種類型的依賴關系。 這意味着產品存儲庫應該將連接字符串作為構造函數參數(因為它是必需的依賴項),並且您的產品處理器應該采用產品ID和產品名稱(如果這是您進行動態查詢的最佳方式)。

因此,假設您的產品存儲庫是單例,您可以在進行注冊時傳遞它(傳遞連接字符串),然后在您的IoC容器中根據您的抽象注冊它。

然后,您可以新建一個產品處理器(傳遞產品ID和產品名稱)並將其注冊為抽象的單例。 然后,您可以使用構造函數注入將產品處理器傳遞到任何需要它的類型。

當然,現在ProductRepository沒有無參數構造函數,並且很難與DI容器一起使用。

相反,大多數DI容器允許您使用參數化構造函數。 實際上,在執行依賴注入時,構造函數注入是建議的方法,這意味着您將擁有非默認構造函數。 具有接受基本類型(例如字符串依賴性)的構造函數可能意味着某些容器將無法為您執行自動連接。 自動布線意味着容器將確定要注射的內容。 但是,使用產品存儲庫,可以通過向容器提供初始化實例(如果需要單個實例)或提供工廠委托(如果每次調用需要新實例)來輕松解決此問題。 這取決於您使用的框架,但它可能如下所示:

container.RegisterSingle(new SqlProductFactory("constring"));

當您在SqlProductFactory的構造函數中提供連接字符串時,您不必將其(使用方法注入)傳遞給工廠,並且您的Arguments類中不需要此連接字符串。

你可以做的是將對象創建與對象查找分離。 DI容器將查找您在啟動時注冊的實例。 此時,您可以將連接字符串作為構造函數參數傳遞到存儲庫。

這就是產品代碼的樣子;

public class ProductRepository : IProductRepority
{
    private readonly string connString;
    public ProductRepository(string conn)
    {
        connString = conn;
    }
}

如果需要,您也可以使用其他類型包裝連接字符串。 重要的是,DI將根據在啟動期間在類型圖上完成的綁定來注入所需的實例。 根據注冊,您可以簡單地從args中提取連接字符串,並將其傳遞給ProductRepository注冊。

編輯

有關如何解決您所述問題,請參閱以下答案。

但是,我真的建議使用現有的IOC包,例如Windsor或nHibernate。 有關詳細信息,請參閱https://stackoverflow.com/questions/2515124/whats-the-simplest-ioc-container-for-c

結束編輯

為什么不將ConnectionString作為屬性添加到IProductRepository。

所以界面是:

public interface IProductRepository
{
  string ConnectionString { get; set; }
  Product GetById(int productId);
  Product GetByName(string productName);
}

處理器變為:

 public void Process(Arguments arguments)
 {
 Product productToProcess;

 var productRepository = new ProductRepository 
      { ConnectionString = arguments.ConnectionString};

 if (!string.IsNullOrEmpty(arguments.ProductName))
    productToProcess = productRepository.GetByName(arguments.ProductName);
 else
    productToProcess = productRepository.GetById(arguments.ProductId);

 // ....
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM