我正在编写一个简单的控制台应用程序,负责连接到数据库,从中选择特定产品(基于提供的标准)并使用此产品进行一些处理。我将命令行参数存储到此类的实例:
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
是否已在GetById
或GetByName
方法中执行任何操作之前被调用。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和容器一无所知。
如果可能的话,我想保持这种方式。
答案 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);
// ....
}