这是依赖注入的错误使用吗?
Is this the wrong use of Dependency Injection?
我正在与一些同事一起开发 MVC 应用程序。所有控制器都使用 Unity 将 ServiceFactory 注入其中...
public HomeController(IServiceFactory serviceFactory)
{
Services = serviceFactory
// Where Services is a property of the Controller
}
现在,我正在编写大量 ViewModel,其中一些 ViewModel 需要访问来自大量服务的对象。所以,我有这样的模式...
public class MyViewModel
{
public MyViewModel(IServiceFactory services)
{
// Do stuff
}
}
我的控制器包含这样的代码...
public ActionResult SomeAction()
{
var model = new MyViewNodel(Services)
// ...
}
我的一些同事认为这不是 DI 的正确用法,但似乎无法阐明原因。
他们说得对吗?如果是,为什么?
我觉得很合适。 ViewModels
依赖于某些服务。因此,不是在 ViewModel
中实例化这些服务,而是通过构造函数 注入 它们。一个非常简单的 DI 案例。
您的同事发出危险信号的原因是您成功地通过服务注入实践了 DI,但您在控制器操作中新建了一个 MyViewModel 实例,"hides" 实现细节从外部。
也就是说,由于此控制器不应直接从其他代码使用,而应从 Web 使用,因此您可以提出这是可以接受的论点。我个人的偏好是,既然你已经开始注入依赖,就不要停下来!
编辑:
正如 James 在评论中提到的那样,最好通过一个独立的注入工厂来解析您的视图模型实例。这样你就可以在同一个控制器中解析多个视图 class.
private readonly IViewModelFactory _factory;
public HomeController(IViewModelFactory factory)
{
_factory = factory;
var model = _factory.GetViewModelInstance();
// Where Services is a property of the Controller
}
注册时
IMyViewModelFactory
针对您的具体 class,解决容器中 IServiceFactory 的注入问题,将其突出显示为注入的依赖项。
public ViewModelFactory(IServiceFactory factory)
这在一定程度上取决于您对设计的看法,但在您的应用程序 IMO 中出现的问题是您将数据与行为混合在一起。您应该为视图提供的视图模型应该是 DTO;纯数据对象。您不希望这些对象有任何行为,因为这对应用程序隐藏了逻辑。视图呈现时执行的所有逻辑应尽可能简单。不仅使关于应用程序代码的推理更简单,而且还使您不必为视图编写自动化测试(这绝对是一件痛苦的事情)。
当您将视图模型用作 'post-back model' 时(这非常常见且方便),让视图模型成为简单 DTO 以外的任何东西都会很快崩溃。 MVC 不能模型绑定你的复杂视图模型,除非你编写一个自定义绑定器将这些依赖项注入其中;或者至少告诉 MVC 如何构造该类型。这是非常不切实际的,并且会导致每个视图模型都有很多额外的代码。虽然这个问题可以通过定义一个单独的 'post-only' 视图模型来解决,但这又会导致重复代码。这两个模型会很快失去同步(我是根据这方面的经验讲的)。
由于您在视图模型中混合了行为,因此您不得不在控制器中手动进行依赖注入(就像您现在所做的那样),或者您需要将此代码提取到工厂中。两者都有缺点。
像现在这样注入那些依赖项,注入代码会污染控制器;它不应该关心的事情。构造函数应该是一个实现细节(只有 class 本身和组合根是关心的),但现在每次你的视图模型的构造函数发生变化时,这个变化都会冒泡到控制器。
将其提取到工厂中可能会解决该问题,但会迫使您为每个控制器编写一个工厂。尽管有些容器包含为您自动生成这些工厂的功能,但您的代码中仍然有这种额外的抽象;如果您将视图模型保持为简陋的 DTO,则不需要抽象。
所以一般来说你应该遵循这个规则:
Create object graphs of stateless services and build them early. After that, pass pure data through the graph.
所以在你的例子中,视图模型就是数据。数据不应依赖于服务;服务应该依赖于数据。您让您的服务(您的控制器)创建该数据(视图模型)并将其传递(将其返回给视图)。
我正在与一些同事一起开发 MVC 应用程序。所有控制器都使用 Unity 将 ServiceFactory 注入其中...
public HomeController(IServiceFactory serviceFactory)
{
Services = serviceFactory
// Where Services is a property of the Controller
}
现在,我正在编写大量 ViewModel,其中一些 ViewModel 需要访问来自大量服务的对象。所以,我有这样的模式...
public class MyViewModel
{
public MyViewModel(IServiceFactory services)
{
// Do stuff
}
}
我的控制器包含这样的代码...
public ActionResult SomeAction()
{
var model = new MyViewNodel(Services)
// ...
}
我的一些同事认为这不是 DI 的正确用法,但似乎无法阐明原因。
他们说得对吗?如果是,为什么?
我觉得很合适。 ViewModels
依赖于某些服务。因此,不是在 ViewModel
中实例化这些服务,而是通过构造函数 注入 它们。一个非常简单的 DI 案例。
您的同事发出危险信号的原因是您成功地通过服务注入实践了 DI,但您在控制器操作中新建了一个 MyViewModel 实例,"hides" 实现细节从外部。
也就是说,由于此控制器不应直接从其他代码使用,而应从 Web 使用,因此您可以提出这是可以接受的论点。我个人的偏好是,既然你已经开始注入依赖,就不要停下来!
编辑:
正如 James 在评论中提到的那样,最好通过一个独立的注入工厂来解析您的视图模型实例。这样你就可以在同一个控制器中解析多个视图 class.
private readonly IViewModelFactory _factory;
public HomeController(IViewModelFactory factory)
{
_factory = factory;
var model = _factory.GetViewModelInstance();
// Where Services is a property of the Controller
}
注册时
IMyViewModelFactory
针对您的具体 class,解决容器中 IServiceFactory 的注入问题,将其突出显示为注入的依赖项。
public ViewModelFactory(IServiceFactory factory)
这在一定程度上取决于您对设计的看法,但在您的应用程序 IMO 中出现的问题是您将数据与行为混合在一起。您应该为视图提供的视图模型应该是 DTO;纯数据对象。您不希望这些对象有任何行为,因为这对应用程序隐藏了逻辑。视图呈现时执行的所有逻辑应尽可能简单。不仅使关于应用程序代码的推理更简单,而且还使您不必为视图编写自动化测试(这绝对是一件痛苦的事情)。
当您将视图模型用作 'post-back model' 时(这非常常见且方便),让视图模型成为简单 DTO 以外的任何东西都会很快崩溃。 MVC 不能模型绑定你的复杂视图模型,除非你编写一个自定义绑定器将这些依赖项注入其中;或者至少告诉 MVC 如何构造该类型。这是非常不切实际的,并且会导致每个视图模型都有很多额外的代码。虽然这个问题可以通过定义一个单独的 'post-only' 视图模型来解决,但这又会导致重复代码。这两个模型会很快失去同步(我是根据这方面的经验讲的)。
由于您在视图模型中混合了行为,因此您不得不在控制器中手动进行依赖注入(就像您现在所做的那样),或者您需要将此代码提取到工厂中。两者都有缺点。
像现在这样注入那些依赖项,注入代码会污染控制器;它不应该关心的事情。构造函数应该是一个实现细节(只有 class 本身和组合根是关心的),但现在每次你的视图模型的构造函数发生变化时,这个变化都会冒泡到控制器。
将其提取到工厂中可能会解决该问题,但会迫使您为每个控制器编写一个工厂。尽管有些容器包含为您自动生成这些工厂的功能,但您的代码中仍然有这种额外的抽象;如果您将视图模型保持为简陋的 DTO,则不需要抽象。
所以一般来说你应该遵循这个规则:
Create object graphs of stateless services and build them early. After that, pass pure data through the graph.
所以在你的例子中,视图模型就是数据。数据不应依赖于服务;服务应该依赖于数据。您让您的服务(您的控制器)创建该数据(视图模型)并将其传递(将其返回给视图)。