2012-09-03 75 views
2

我正在编写一个简单的控制台应用程序,负责连接数据库,从中选择特定的产品(基于提供的条件),并使用此产品进行一些处理。我将命令行参数存储到此类的一个实例中:构造函数注入和注入依赖关系的初始化

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

     // .... 
    } 
} 

,但什么我不t喜欢的设计是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容器。在我的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和容器一无所知。

如果可能的话,我想保留它。

回答

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

     // .... 
    } 
} 
+0

是不是连接字符串泄漏抽象?你已经在你的书中陈述了这一点。也许,在这种情况下(连接字符串作为用户输入),这不是很明确吗? – mnn

+0

是的,你可以争辩说,但我只是使用OP的对象模型的那部分,以便不会一下子搞乱所有东西:) –

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

// .... 
} 
+0

使用该属性与使用'Initialize'方法完全相同(我在我的问题中提到它)。如果可能的话,我想避免它,因为在问题中陈述的原因。 –

3

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

因此,假设您的产品存储库是一个单身人士,那么在您进行注册(传递连接字符串)时,您会将其更新,然后在您的抽象中将其注册到IoC容器中。

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

+0

请参阅我所做的修改。我很抱歉没有在原始问题中提供这些信息。问题是,在我向容器注册依赖关系的时候,我还不知道连接字符串的值。这同样适用于产品ID和名称。 –

+0

连接字符串,产品ID和产品名称是否来自命令行参数?即Main方法的'args'参数?在容器中注册类型的点?您只需要更改注册即可使用您在Main方法中实例化的实例。 – devdigital

2

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

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

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

当您提供在SqlProductFactory的构造连接字符串,你不会把它传递(使用方法注入)来工厂,并且您的Arguments类中不需要此连接字符串。

+0

您可以查看我编辑的编辑吗?我正在使用Ninject和编辑细节我正在做的确切方式。你的答案如何适用于这种情况? –

2

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

这就是产品代码的样子;

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

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

+0

我没有配置文件 - 连接字符串存储在'Arguments'实例中。我可以添加一个'IDbConnection'依赖项,但我觉得它只是将问题从'IProductRepository'移动到'IDbConnection'。 –

+0

这是否意味着每个参数的连接字符串都不相同?如果不是,你不需要通过论证来传递它。 –

相关问题