3

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

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
  • 不要使用构造函数注入,而是使用服务定位器模式来实例化ProductRepository. 我不太喜欢 Service Locator,但这可能是唯一可能的解决方案。

有没有更好的选择?

编辑:从答案中我看到我应该发布更多的上下文。我使用 Ninject 作为我的 DI 容器。在Main我的 Program.cs 中的方法中,我将所有依赖项注册到容器并实例化用作应用程序入口点的类:

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 和容器一无所知。

如果可能的话,我想保持这种状态。

4

5 回答 5

4

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

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

        // ....
    }
}
于 2013-03-13T07:26:36.550 回答
3

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

因此,假设您的产品存储库是单例的,您将在注册时将其更新(传递连接字符串),然后在您的 IoC 容器中针对您的抽象进行注册。

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

于 2012-09-03T11:08:35.770 回答
2

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

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

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

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

于 2012-09-03T11:22:28.817 回答
2

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

这就是产品代码的样子;

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

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

于 2012-09-03T11:26:38.153 回答
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);

 // ....
}
于 2012-09-03T11:08:26.127 回答