依赖注入作为一种实践,旨在引入抽象(或seams)来解耦不稳定的依赖关系。易失性依赖项是一个类或模块,其中可以包含不确定性行为,或者通常是您希望能够替换或拦截的内容。
有关易失性依赖项的更详细讨论,请参阅本免费阅读简介的第 1.3.2 节 of my book.
因为你的FileLogger
写入磁盘,其中包含不确定性行为。为此,您介绍了ILoggable
抽象。这使得消费者能够从FileLogger
实现,并允许您 - 稍后 - 当出现此类要求时,轻松地交换它FileLogger
实施与SqlLogger
记录到 SQL 数据库的实现,或者甚至有一个将调用转发到两个数据库的实现FileLogger
or the SqlLogger
.
然而,为了能够成功地将消费者与其不稳定的依赖关系解耦,您需要inject对消费者的依赖。有以下三种常见模式可供选择:
-
构造函数注入- 依赖关系被静态定义为类的参数列表instance构造函数。
-
房产注入- 依赖项通过可写注入到消费者中instance特性。这种模式有时也称为“setter 注入”,尤其是在 Java 世界中。
-
方法注入- 依赖项作为方法参数注入到消费者中。
构造函数注入和属性注入都适用inside应用程序的启动路径(也称为成分根)并要求使用者将依赖项存储在私有字段中以供以后重用。这要求构造函数和属性是实例成员,即非静态。构造函数注入通常优于属性注入,因为属性注入会导致时间耦合。静态构造函数不能有任何参数,静态属性会导致环境上下文反模式(参见第 5.3 节)——这会妨碍可测试性和可维护性。
另一方面,应用方法注入outside组合根,它确实not存储任何提供的依赖项,但仅使用它。这是一个来自早期参考:
// This method calculates the discount based on the logged in user.
// The IUserContext dependency is injected using Method Injection.
public static decimal CalculateDiscountPrice(decimal price, IUserContext context)
{
// Note that IUserContext is never stored - only used.
if (context == null) throw new ArgumentNullException("context");
decimal discount = context.IsInRole(Role.PreferredCustomer) ? .95m : 1;
return price * discount;
}
因此,方法注入是三种模式中唯一可以同时应用于实例和静态类的模式。
当应用方法注入时,该方法的consumer必须提供依赖项。然而,这确实意味着消费者本身必须通过构造函数、属性或方法注入提供该依赖项。例如:
public class ProductServices : IProductServices
{
private readonly IProductRepository repository;
private readonly IUserContext userContext;
public ProductServices(
IProductRepository repository,
IUserContext userContext) // <-- Dependency applied using Ctor Injection
{
this.repository = repository;
this.userContext = userContext;
}
public decimal CalculateCustomerProductPrice(Guid productId)
{
var product = this.repository.GetById(productId);
return CalculationHelpers.CalculateDiscountPrice(
product.Price,
this.userContext); // <-- Dep forwarded using Method Injection
}
}
你的静态例子LogService
创造了FileLogger
它的构造函数内部是紧密耦合代码的一个很好的例子。这被称为控制狂反模式(第 5.1 节)或者一般可以看作是DIP 违规——这是opposite of DI.
为了防止易失依赖项的紧密耦合,最好的方法是LogService
非静态并将其易失性依赖项注入到其唯一的公共构造函数中:
public class LogService
{
private readonly ILoggable _logger;
public LogService(ILoggable logger)
{
_logger = logger;
}
public void WriteLine(string message) ...
}
这可能会违背你的目的LogService
阶级,因为现在消费者通过注射会过得更好ILoggable
直接代替注入LogService
。但这让您回到了为什么您可能首先想要将该类设为静态的原因,即您有很多需要记录的类,并且注入起来感觉很麻烦ILoggable
到所有这些构造函数中。
但是,这可能是由代码中的另一个设计问题引起的。要理解这一点,您可能需要阅读这个问答了解可以进行哪些设计更改以允许更少的类依赖于您的记录器类。