正在注入的构造函数注入和初始化依赖项

时间:2012-09-03 10:54:52

标签: c# oop dependency-injection

我正在编写一个简单的控制台应用程序,负责连接到数据库,从中选择特定产品(基于提供的标准)并使用此产品进行一些处理。我将命令行参数存储到此类的实例:

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。显而易见的缺点是,我现在需要检查Initialize是否已在GetByIdGetByName方法中执行任何操作之前被调用。
  • 不要使用构造函数注入,而是使用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和容器一无所知。

如果可能的话,我想保持这种方式。

5 个答案:

答案 0 :(得分:4)

我同意当前的界面设计是漏洞抽象,所以让我们这样定义:

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

What you need then is an Abstract Factory that can create an instance of IProductRepository for you.

所以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);
        }

        // ....
    }
}

答案 1 :(得分:3)

我不确定为什么你需要对命令行参数进行建模?您应该最小化每种类型的依赖关系。这意味着产品存储库应该将连接字符串作为构造函数参数(因为它是必需的依赖项),并且您的产品处理器应该采用产品ID和产品名称(如果这是您进行动态查询的最佳方式)。 / p>

因此,假设您的产品存储库是单例,您可以在注册时(传递连接字符串)将其新建,然后在您的IoC容器中根据您的抽象注册它。

然后,您可以新建一个产品处理器(传入产品ID和产品名称)并将其注册为抽象的单例。然后,您可以使用构造函数注入将产品处理器传递到任何需要它的类型。

答案 2 :(得分:2)

  

当然,现在ProductRepository没有无参数   构造函数很难与DI容器一起使用。

相反,大多数DI容器允许您使用参数化构造函数。实际上,在执行依赖注入时,构造函数注入是建议的方法,这意味着您将拥有非默认构造函数。具有接受基本类型(例如字符串依赖性)的构造函数可能意味着某些容器将无法为您执行自动连接。自动布线意味着容器将确定要注射的内容。但是,使用产品存储库,可以通过向容器提供初始化实例(如果需要单个实例)或提供工厂委托(如果每次调用需要新实例)来轻松解决此问题。这取决于您使用的框架,但它可能如下所示:

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

当您在SqlProductFactory的构造函数中提供连接字符串时,您不必将其(使用方法注入)传递给工厂,并且您的{中不需要此连接字符串{1}}上课。

答案 3 :(得分:2)

您可以做的是将对象创建与对象查找分离。 DI容器将查找您在启动时注册的实例。此时,您可以将连接字符串作为构造函数参数传递给存储库。

这就是产品代码的样子;

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

如果需要,您也可以使用其他类型包装连接字符串。重要的是,DI将根据在启动期间在类型图上完成的绑定来注入所需的实例。根据注册,您只需从args中提取连接字符串并将其传递给ProductRepository注册。

答案 4 :(得分:0)

修改

请参阅以下答案,了解如何解决您所陈述的问题。

但是,我真的建议使用现有的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);

 // ....
}