我对使用Autofac的Dispose()
实现中的IDisposable
方法有点困惑
说我的对象有一定的深度:
Controller
依赖于IManager
;Manager
依赖于IRepository
;Repository
依赖于ISession
;ISession
是IDisposable
。这导致以下对象图:
new Controller(
new Manager(
new Repository(
new Session())));
我是否还需要使我的Manager和Repository实现IDisposable,并在Controller中调用Manager.Dispose(),在Manager中调用Repository.Dispose()等,或者Autofac会自动知道我的调用堆栈中哪些对象需要正确处理? Controller对象已经是IDisposable,因为它派生自基本ASP.NET Web API控制器
资源的一般规则是:
拥有该资源的人负责处理它。
这意味着如果一个类拥有一个资源,它应该以与它创建它相同的方法处理它,或者如果这不可能,这通常意味着拥有类必须实现IDisposable
,因此它可以将资源部署在其中Dispose
方法。
但重要的是要注意,一般来说,如果一个类负责创建它,那么它只拥有一个资源。但是当注入资源时,这意味着该资源在消费者之前就已经存在了。消费者没有创建资源,在这种情况下应该不处理它。虽然我们可以将资源的所有权传递给使用者(并在类的文档中传达所有权的传递),但通常不应传递所有权,因为这会使代码复杂化并使应用程序非常脆弱。
虽然在某些情况下转移对象所有权的策略可能有意义,例如对于可重用API的一部分类型(如System.IO.StreamReader
),在处理作为对象图的一部分的组件时,它总是很糟糕(我们所谓的injectables)。我将在下面解释原因。
因此,即使您的Controller
依赖于需要处理的依赖关系,您的控制器也不应该丢弃它们。原因很简单:
ObjectDisposedException
。IDisposable
。这意味着这种抽象漏掉了实现细节;这是对Dependency Inversion Principle的违反。在抽象上实现IDisposable
时,您正在泄漏实现细节,因为该抽象的每个实现都不太可能需要确定性处理,因此您在考虑某个实现时定义了抽象。消费者不应该对实施有任何了解,无论是否需要确定性处置。IDisposable
也会导致你违反Interface Segregation Principle,因为抽象现在包含一个额外的方法(即Dispose
),并非所有消费者都需要调用。他们可能不需要打电话,因为 - 我们已经意识到 - 资源可能比消费者更长久。在这种情况下让它实现IDisposable
是危险的,因为任何人都可以调用Dispose
导致应用程序中断。如果我们对测试非常严格,这也意味着我们必须测试消费者没有调用Dispose方法。考虑一下这将导致的额外测试代码。这是需要编写和维护的代码。所以相反,我们应该只让实现实现IDisposable
。这使得抽象的任何消费者都不会怀疑它是否应该调用Dispose
(因为没有Dispose
方法来调用抽象)。
现在,因为只有实现实现了IDisposable
,并且只有你的Composition Root创建了这个实现,所以它的组合根负责处理它。如果你的DI容器创建了这个资源,它也应该处理它。 Autofac实际上会为您做到这一点。你可以轻松测试这个。如果您在不使用DI容器(a.k.a。Pure DI)的情况下连接对象图形,则意味着您必须自己将这些对象放置在Composition Root中。
要意识到这会使您的代码更简单。在抽象上实现IDisposable
并让消费者处理它们的依赖关系将导致IDisposable
像病毒一样通过你的系统传播并污染你的代码库。
它展开,因为您总是可以想到需要清理其资源的抽象实现,因此您必须在每个抽象上实现IDisposable
。这意味着每个需要一个或多个依赖项的实现也必须实现IDisposable
,这会爬上对象图。这为系统中的每个类增加了大量代码和不必要的复杂性。