组件应该不处理任何注入的依赖项。主要原因是:
- 该组件没有创建它们,因此不知道是否应该释放这些依赖项。
- 消费者甚至不应该意识到依赖项是一次性的。
组件依赖于寿命更长的服务是很常见的。如果消费组件处理该依赖项,则应用程序将中断,因为该依赖项在配置为使用时无法再使用。这是一个简单的例子:
// Singleton
private static readonly IRepository<User> repository = new UserRepository();
public IController CreateController(Type controllerType) {
if (controllerType == typeof(UserController)) {
return new UserController(repository);
}
// ...
}
此示例包含一个单例UserRepository 和一个瞬态UserController。对于每个请求,都会创建一个新的UserController(只需想象一个 ASP.NET MVC 应用程序,这将开始有意义)。如果UserController 将释放UserRepository,则下一个请求将获得一个UserController,该UserController 将依赖于已释放的UserRepository。这显然很糟糕。
但除此之外,IRepository<T> 应该不实现IDisposable。实现IDisposable 意味着抽象泄漏了实现细节,因此违反了Dependency Inversion Principle,它指出:
抽象不应依赖于细节。细节应该取决于
抽象。
仅当您绝对 100% 确定您将需要处理的该抽象的所有实现时,在抽象上实现 IDisposable 才有意义。但这几乎从来没有发生过。想象一下在你的单元测试中有一个FakeRepository<T> 实现。这种虚假的实现永远不需要处置,因此并非所有实现都需要处置,而且您正在泄漏实现细节。
这只是意味着您应该将IDisposable 接口移至实现。例如:
public interface IRepository<T> { }
public class UserRepository : IRepository<User>, IDisposable { }
请注意,在抽象上使用IDisposable 接口,虽然并非所有消费者都应该调用Dispose,但这也意味着您违反了Interface Segregation Principle 的规定:
不应强迫任何客户端依赖它不使用的方法。
这样做的好处是,消费组件(例如UserController)不可能意外调用Dispose(),这可能会破坏系统。
另一个优点是,由于组件不需要释放它们的依赖关系,因此对于大多数组件来说,不会留下任何释放逻辑,从而使系统变得更加简单和可维护。