构造函数注入最佳实践

时间:2014-07-28 18:34:00

标签: c# asp.net-mvc design-patterns dependency-injection ninject

我有3个服务组件,一个负责某种数据序列化的低级服务,一个负责协调保存/加载,一个负责API发布的MVC控制器。

3个组成部分中的每一个逻辑上都指向另一个“下方”。中间服务具有另一个参数,该参数在运行时基于请求数据而已知。从这3个组件中,控制器和中间服务由类表示(引入接口没有意义,因为没有任何模拟),最低级别由接口重复,使其可用于单元测试中间服务或控制器。我想使用DI(特别是Ninject)来构建我的控制器类。我的问题是,是否存在任何处理此方案的最佳实践。目前我看到两种实施方式。 (为了清楚起见,省略了验证,正确实施。)

首先,这是中间服务和低级接口的示例实现。

public interface ISerializer {
  void Serialize(object data);  
}

public class MyService {
  private string _dataId;
  private ISerializer _serializer;
  public MyService(string dataId, ISerializer serializer) {
    _serializer = serializer;
    _dataId = dataId;
  }
  public bool CanProcess(MyDTO data) {
    ...
  }
  public void DoSomeProcessing(MyDTO data) {
    ...
  }
}

版本1:将整个中间服务作为工厂注入控制器

public class MyController : Controller {
  private Func<string, MyService> _myServiceFactory;
  public MyController(Func<string, MyService> myServiceFactory) {
    _myServiceFactory = myServiceFactory;
  }
  ...
  [HttpPost]
  public JsonResult Process(string dataId, MyDTO model) {
    using (var myService = _myServiceFactory(dataId)) {
      ...
      if (myService.CanProcess(model))
        myService.DoSomeProcessing(model);
      ...
      return Json("ok");
    }
  }  
}

版本2:直接向控制器注入较低级别的接口,并“手动”实例化中间服务。

public class MyController : Controller {
  private ISerializer _serializer;
  public MyController(ISerializer serializer) {
    _serializer = serializer;
  }
  ...
  [HttpPost]
  public JsonResult Process(string dataId, MyDTO model) {
    using (var myService = new MyService(dataId, _serializer) {
      ...
      if (myService.CanProcess(model))
        myService.DoSomeProcessing(model);
      ...
      return Json("ok");
    }
  }  
}

哪一个更合适,或者我应该选择一个完全不同的解决方案?

1 个答案:

答案 0 :(得分:1)

首先,我喜欢我的服务是无国籍的,所以我不喜欢传递dataId的想法 到服务的构造函数。当服务无国籍时,它们更安全。如果他们当前处于有效状态,您可以调用他们的方法而不用担心。它还使测试和模拟它们变得更容易。您还可以减少已用内存量,因为您只需要一个无状态服务实例。

如果您将dataId移动到DoSomeProcessing作为参数,则可以使用Ninject轻松实例化MyService,并自动注入正确的ISerializer实现。

但是,如果你坚持将它传递给构造函数“Version 1”,那就非常接近我认为好的了。当构造函数中还需要数据参数时,工厂是让DI注入依赖项的好方法。我会将MyServiceFactory注入控制器。我会为它创建另一个类:

public class MyServiceFactory : IMyServiceFactory // an interface to me able to mock it if needed
{
    ISerializer _serializer;
    MyServiceFactory(ISerializer serializer){  // here Ninject can inject the dependency
        _serializer = serializer;
    }

    IMyService Create(int dataId){ // here you can pass additional parameter
        return new MyService(dataId, _serializer);
    }
}

通过这种方式,您可以轻松避免硬依赖性,并使代码更易于维护和更易于测试。

“版本2”错了。如果你想测试你的控制器或用另一个实现替换MyService - 你就会陷入困境。您将不得不进行大量繁琐的重构(取决于使用量)。最后,你最终会得到与我上面建议类似的东西。 :)