依赖注入作为一种实践,旨在引入抽象(或
接缝)来解耦易变的依赖关系。易变的依赖关系是指一个类或模块,除其他事项外,可能包含非确定性行为,或者一般来说,是您希望能够替换或拦截的内容。
有关易变依赖关系的更详细讨论,请参阅本免费可阅读介绍的1.3.2节以及我的书。
因为你的`FileLogger`写入磁盘,所以它包含着不确定的行为。因此,你引入了`ILoggable`抽象。这使得消费者与`FileLogger`的实现解耦,并且在需要时,你可以轻松地将这个`FileLogger`实现替换为一个将日志记录到SQL数据库的`SqlLogger`实现,甚至可以有一个将调用转发到`FileLogger`或`SqlLogger`的实现。
然而,为了成功地将消费者与其易变的依赖解耦,你需要将该依赖注入到消费者中。有三种常见的模式可供选择:
- 构造函数注入 - 依赖项被静态定义为类的实例构造函数的参数列表。
- 属性注入 - 依赖项通过可写的实例属性注入到使用者中。这种模式有时也被称为“setter注入”,尤其在Java世界中。
- 方法注入 - 依赖项作为方法参数注入到使用者中。
构造函数注入和属性注入都应用在应用程序的启动路径(也称为
组合根)内,并要求消费者将依赖项存储在私有字段中以供以后重用。这要求构造函数和属性是实例成员,即非静态的。通常情况下,构造函数注入优于属性注入,因为属性注入会导致
时间耦合。静态构造函数不能有任何参数,而静态属性会导致
环境上下文反模式(参见第5.3节)——这会影响可测试性和可维护性。
方法注入,另一方面,是应用在组合根之外的,它不会存储任何提供的依赖项,而只是简单地使用它。以下是一个来自
之前的参考的示例:
public static decimal CalculateDiscountPrice(decimal price, IUserContext context)
{
if (context == null) throw new ArgumentNullException("context");
decimal discount = context.IsInRole(Role.PreferredCustomer) ? .95m : 1;
return price * discount;
}
方法注入是三种模式中唯一适用于实例和静态类的模式。
在应用方法注入时,方法的“使用者”必须提供依赖项。然而,这也意味着使用者本身必须通过构造函数、属性或方法注入来提供该依赖项。例如:
public class ProductServices : IProductServices
{
private readonly IProductRepository repository;
private readonly IUserContext userContext;
public ProductServices(
IProductRepository repository,
IUserContext userContext)
{
this.repository = repository;
this.userContext = userContext;
}
public decimal CalculateCustomerProductPrice(Guid productId)
{
var product = this.repository.GetById(productId);
return CalculationHelpers.CalculateDiscountPrice(
product.Price,
this.userContext);
}
}
你在构造函数中创建
FileLogger
的静态
LogService
的例子是紧密耦合代码的一个很好的例子。这被称为
控制狂反模式(第5.1节),或者一般可以看作是
DIP违规——这是DI的
相反。
为了防止易变依赖的紧密耦合,最好的方法是将
LogService
设置为非静态,并将其易变依赖注入到其唯一的公共构造函数中:
public class LogService
{
private readonly ILoggable _logger;
public LogService(ILoggable logger)
{
_logger = logger;
}
public void WriteLine(string message) ...
}
这很可能违背了你的LogService类的目的,因为现在消费者更好地通过直接注入ILoggable而不是注入LogService来使用。但这又让你回到了最初想要将该类设为静态的原因,即你有许多需要记录日志的类,将ILoggable注入所有这些构造函数可能感觉很繁琐。
然而,这可能是你代码中另一个设计问题导致的。为了理解这一点,你可能想要阅读这个问题和答案(link1:this q&a)来了解一些设计改变,以减少依赖于你的日志记录器类的类的数量。