我尝试在我的类中注入依赖项时遇到问题。当我被困在这里时,我只是试着去了解更多关于简单注射和DI的信息。
所以这是我的主要方法:
static void Main(string[] args)
{
var container = new Container();
// Registrations here
container.RegisterAll<ISimpleLogger>(typeof(DebugLogger), typeof(ConsoleLogger));
container.Verify();
Product p = new Product();
p.TestLog();
Console.ReadKey();
}
这是我的其他课程:
public interface ISimpleLogger
{
void Log(string content);
}
public class DebugLogger : ISimpleLogger
{
public void Log(string content)
{
Debug.WriteLine(content);
}
}
public class ConsoleLogger : ISimpleLogger
{
public void Log(string content)
{
Console.WriteLine(content);
}
}
public class Product
{
public string Name { get; private set; }
public decimal Price { get; private set; }
public Product(string name, decimal price)
{
Name = name;
Price = price;
}
private readonly ISimpleLogger[] loggers;
public Product(params ISimpleLogger[] _loggers)
{
this.loggers = _loggers;
}
public void TestLog()
{
foreach (var item in loggers)
{
item.Log("testing...");
}
}
public static List<Product> GetSampleProducts()
{
return new List<Product>
{
new Product { Name="West Side Story", Price = 9.99m },
new Product { Name="Assassins", Price=14.99m },
new Product { Name="Frogs", Price=13.99m },
new Product { Name="Sweeney Todd", Price=10.99m}
};
}
}
1)我看不到输出&#34;测试......&#34;任何地方。
2)调试我的应用程序,当调用Product构造函数时,它不会注入依赖项,因此我的数组保留0项。
应该是什么?设置容器时配置错误?
答案 0 :(得分:2)
问题非常简单,您似乎已正确配置container
,但您没有要求它创建Product
对象,因此能够注入依赖项。
而不是new
自己Product
,请container
为你做这件事......
static void Main(string[] args)
{
var container = new Container();
// Registrations here
container.RegisterAll<ISimpleLogger>(typeof(DebugLogger), typeof(ConsoleLogger));
container.Verify();
Product p = container.GetInstance<Product>();
p.TestLog();
Console.ReadKey();
}
我认为您还需要更改Product
的构造函数以取IEnumerable<>
private readonly IEnumerable<ISimpleLogger> loggers;
public Product(IEnumerable<ISimpleLogger> loggers)
{
this.loggers = loggers;
}
答案 1 :(得分:2)
使用依赖注入,我们构建组件的对象图。组件是我们的应用程序中包含应用程序行为的类。这种对象图通常由长期服务组成。这些服务本身不应包含任何州。应该使用方法调用将状态(或运行时数据)推送到此对象图中。只要你违反这个基本原则,你就会遇到麻烦。
您的Product
班级是一个实体。这是一个包含状态的短期对象。防止您的DI库构建包含状态的对象。这会导致模糊性和可维护性问题。
在您的情况下已经发生这种情况,因为您希望创建一个Product
类,您希望它是不可变的。所以这意味着它需要通过构造函数初始化。另一方面,您还希望将其依赖项注入到构造函数中,您将为其构建第二个构造函数。但是你只能调用一个构造函数;所以你的Product
要么是没有依赖关系的有效产品,要么是带有依赖关系的无效产品。
虽然您可以通过将两个构造函数合并在一起来解决这个问题,但这会导致相同的麻烦,因为现在您将不得不要求DI容器为您构建此产品,但DI容器一无所知它必须提供的原语(在你的情况下是名称和价格)。
在我的项目中,我通常使用anemic domain model。这意味着我的实体在其中没有任何逻辑。相反,我使用commands和queries作为我的域模型动词的定义。其他人不喜欢这样,并且喜欢将域逻辑作为域对象的一部分(虽然这两个模型不是互斥的,但是一些开发人员结合使用这些概念)。由于域逻辑有时需要使用服务,因此这些实体显然需要提供这些依赖性。但这并不意味着你不得不在这里使用构造函数注入。
在这种情况下,方法注入更方便。这意味着您的Product
课程将如下所示:
public class Product
{
public string Name { get; private set; }
public decimal Price { get; private set; }
public Product(string name, decimal price) {
Name = name;
Price = price;
}
public void TestLog(ISimpleLogger[] loggers) {
foreach (var item in loggers)
{
item.Log("testing...");
}
}
}
因此,我们不是通过构造函数传递依赖项,而是通过实际需要它的方法传递它。方法注入效果更好,因为:
当然,注入这些依赖项的责任现在转移到调用此域方法的人(在您的情况下为TestLog
方法)。但这通常不是问题,因为此消费者将是一个普通的应用程序组件,您可以再次应用构造函数注入。这是一个例子:
public class LogProductCommandHandler : ICommandHandler<LogProduct>
{
private readonly IRepository<Product> productRespository;
private readonly ISimpleLogger[] loggers;
public LogProductCommandHandler(IRepository<Product> productRespository,
ISimpleLogger[] loggers) {
this.productRespository = productRespository;
this.loggers = loggers;
}
public void Handle(LogProduct command) {
Product product = this.productRepository.GetById(command.ProductId);
product.TestLog(this.loggers);
}
}
容器可以解析此LogProductCommandHandler
。请注意所有运行时数据&#39;流量&#39;通过这里的对象图。我们有LogProduct
消息(命令),其中包含运行时数据。它被传递给Handle
方法。此消息包含ProductId
值,并将其传递到存储库的GetById
方法,该方法将返回Product
(再次运行时数据)。
但还有一件事。防止向消费者注入事物清单。注入T[]
数组,IEnumerable<T>
或List<T>
通常意味着您正在泄漏实施细节。消费者不应该知道或担心在你的情况下可能有多个实现 - 记录器。注入集合会使消费者复杂化,因为它必须迭代集合。不仅如此,依赖该集合的每个消费者都需要迭代这个集合。这不仅使所有这些消费者的代码变得不必要地复杂化,它将使您的代码更难以改变。在某个时间点,您会想要改变处理这些记录器的方式。也许您希望继续记录到下一个记录器,即使第一个记录器引发了异常。我确定你知道如何编程这样的东西;解决这个问题并不困难。但它会让你通过你的代码库进行彻底的改变来实现这一目标。
相反,请使用Composite design pattern。使用复合模式,您可以在实现相同抽象的组件后面隐藏一些抽象元素的集合。 ISimpleLogger
的复合实现可能如下所示:
public sealed SimpleLoggerComposite : ISimpleLogger
{
private readonly IEnumerable<ISimpleLogger> loggers;
public SimpleLoggerComposite(IEnumerable<ISimpleLogger> loggers) {
this.loggers = loggers;
}
public void Log(string message) {
foreach (var logger in this.loggers) {
logger.Log(message);
}
}
}
我们将foreach
循环移至SimpleLoggerComposite
。现在我们可以按如下方式注册SimpleLoggerComposite
:
// Simple Injector v3.x
container.RegisterSingleton<ISimpleLogger, SimpleLoggerComposite>();
container.RegisterCollection<ISimpleLogger>(
new[] { typeof(DebugLogger), typeof(ConsoleLogger) });
// Simple Injector v2.x
container.RegisterSingle<ISimpleLogger, SimpleLoggerComposite>();
container.RegisterAll<ISimpleLogger>(
new[] { typeof(DebugLogger), typeof(ConsoleLogger) });
现在,应用程序的其余部分可以完全依赖于ISimpleLogger
而不是ISimpleLogger[]
。例如:
public class Product
{
...
public void TestLog(ISimpleLogger logger) {
logger.Log("testing...");
}
}
现在,TestLog
方法中的代码变得非常无聊。但这显然是一件好事。软件开发的诀窍是使看起来很简单的软件: - )