你的问题又回到了所有权原则:
拥有资源所有权的人应该处置它。
虽然所有权可以转让,但您通常不应该这样做。在您的情况下,IDbConnection 的所有权从ordersService 转移到OrdersRepository(因为OrdersRepository 处理了连接)。但在很多情况下OrdersRepository 无法知道连接是否可以被释放。它可以在整个对象图中重用。所以一般来说,你不应该释放通过构造函数传递给你的对象。
另一件事是依赖项的使用者通常不知道依赖项是否需要处理,因为是否需要处理依赖项是一个实现细节。此信息可能在界面中不可用。
因此,请将您的 OrdersRepository 重构为以下内容:
public class OrdersRepository : IOrdersRepository {
private IDbConnection _db;
public OrdersRepository(IDbConnection db) {
_db = db;
}
}
由于OrdersRepository 没有所有权,IDbConnection 不需要处置IDbConnection,您也不需要实现IDisposable。这明确地将处理连接的责任转移到OrdersService。但是,ordersService 本身不需要 IDbConnection 作为依赖项;它只取决于IOrdersRepository。那么为什么不将构建对象图的责任也从OrdersService 中移出:
public class OrdersService : IDisposable {
private readonly IOrdersRepository _orders;
public ordersService(IOrdersRepository ordersRepo) {
_orders = ordersRepo;
}
}
由于ordersService 自己没有什么可处置的,所以没有必要实现IDisposable。而且由于它现在只有一个构造函数来获取所需的依赖项,因此该类变得更容易维护。
所以这将创建对象图的责任转移到OrdersController。但是我们也应该对OrdersController 应用相同的模式:
public class OrdersController : Controller {
private ordersService _orderService;
public OrdersController(ordersService o) {
_orderService = o;
}
}
同样,这个类变得更容易掌握并且它不需要处理任何东西,因为它不拥有或取得任何资源的所有权。
当然,我们只是移动并推迟了我们的问题,因为显然我们仍然需要创建我们的OrdersController。但不同的是,我们现在将构建对象图的职责转移到了应用程序中的一个位置。我们称这个地方为Composition Root。
依赖注入框架可以帮助您使您的组合根可维护,但即使没有 DI 框架,您也可以通过实现自定义 ControllerFactory 在 MVC 中轻松构建对象图:
public class CompositionRoot : DefaultControllerFactory {
protected override IController GetControllerInstance(
RequestContext requestContext, Type controllerType) {
if (controllerType == typeof(OrdersController)) {
var connection = new Ajx.Dal.DapperConnection().getConnection();
return new OrdersController(
new OrdersService(
new OrdersRepository(
Disposable(connection))));
}
else if (...) {
// other controller here.
}
else {
return base.GetControllerInstance(requestContext, controllerType);
}
}
public static void CleanUpRequest() }
var items = (List<IDisposable>)HttpContext.Current.Items["resources"];
if (items != null) items.ForEach(item => item.Dispose());
}
private static T Disposable<T>(T instance)
where T : IDisposable {
var items = (List<IDisposable>)HttpContext.Current.Items["resources"];
if (items == null) {
HttpContext.Current.Items["resources"] =
items = new List<IDisposable>();
}
items.Add(instance);
return instance;
}
}
您可以像这样将您的自定义控制器工厂挂接到您的 MVC 应用程序的 Global asax 中:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
ControllerBuilder.Current.SetControllerFactory(
new CompositionRoot());
}
protected void Application_EndRequest(object sender, EventArgs e)
{
CompositionRoot.CleanUpRequest();
}
}
当然,当您使用依赖注入框架时,这一切都会变得容易得多。例如,当您使用 Simple Injector(我是 Simple Injector 的主要开发人员)时,您可以将所有这些替换为以下几行代码:
using SimpleInjector;
using SimpleInjector.Integration.Web;
using SimpleInjector.Integration.Web.Mvc;
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
var container = new Container();
container.RegisterPerWebRequest<IDbConnection>(() =>
new Ajx.Dal.DapperConnection().getConnection());
container.Register<IOrdersRepository, OrdersRepository>();
container.Register<IOrdersService, OrdersService>();
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
container.Verify();
DependencyResolver.SetResolver(
new SimpleInjectorDependencyResolver(container));
}
}
上面的代码中发生了一些有趣的事情。首先,对Register 的调用告诉Simple Injector,它们需要返回某个实现,应该在请求给定抽象时创建。接下来,Simple Injector 允许向Web Request Lifestyle 注册类型,这确保给定的实例在网络请求结束时被释放(就像我们在Application_EndRequest 中所做的那样)。通过调用RegisterMvcControllers,Simple Injector 将为您批量注册所有控制器。通过为 MVC 提供 SimpleInjectorDependencyResolver,我们允许 MVC 将控制器的创建路由到 Simple Injector(就像我们对控制器工厂所做的那样)。
虽然此代码一开始可能有点难以理解,但当您的应用程序开始增长时,使用依赖注入容器变得非常有价值。 DI 容器将帮助您保持 Composition Root 的可维护性。