我总是看到人们总是在谈论使用像Ninject,Unity,Windsor这样的框架来做依赖解析器和注入。以下面的代码为例:
public class ProductsController : ApiController
{
private IProductRepository _repository;
public ProductsController(IProductRepository repository)
{
_repository = repository;
}
}
我的问题是:为什么我们不能简单地写为:
public class ProductsController : ApiController
{
private IProductRepository _repository;
public ProductsController() :this(null)
{}
public ProductsController(IProductRepository repository)
{
_repository = repository?? new ProductRepository();
}
}
在这种情况下,我们似乎不需要任何框架,即使是我们可以轻松模拟的单元测试。
那么这些框架的真正目的是什么?
提前致谢!
答案 0 :(得分:3)
在这种情况下,您的ProductsController
仍然依赖于低级别组件(您的案例中具体ProductRepository
),这违反了Dependency Inversion Principle。这是否是一个问题取决于多种因素,但它会导致以下问题:
ProductRepository
的创建在整个应用程序中仍然重复,导致您在ProductRepository
机会构造函数的整个应用程序中进行彻底更改(假设ProductRepository
在更多地方使用,这是非常合理的),这将是一个Open/Closed Principle violation。ProductService
与装饰器或拦截器一起包装时,会导致您进行彻底的更改,这会增加您确实不喜欢的横切关注点(例如日志记录,审计跟踪,安全过滤等)我想在你的所有存储库中重复该代码(同样是OCP违规)。ProductsController
了解ProductsRepository
,可能可能会成为问题,具体取决于您正在编写的应用程序的大小和复杂程度。所以这不是关于框架的使用,而是关于应用软件设计原则。如果您决定遵循这些原则来使您的应用程序更易于维护,那么Ninject,Autofac和Simple Injector等框架可以帮助您使应用程序的启动路径更易于维护。但是没有任何东西妨碍你在不使用任何工具或库的情况下应用这些原则。
答案 1 :(得分:3)
小免责声明:我是狂热的 Unity用户,这是我的2美分。
@democodemonkey和@thumbmunkeys已经说过,你们紧紧地结合了两个班级。让我们说一些类(让它们是ProductsThingamajigOne和ProductsThingamajigTwo)使用ProductsController,并使用它的默认构造函数。如果架构师决定系统不应使用将Products保存到文件中但应使用数据库或云存储的ProductsRepository,该怎么办?对课程有什么影响?
如果存储库基于数据库,则可能需要为其提供ConnectionString。如果它基于文件,您可能需要为其提供一类设置,提供保存文件的确切路径 - 事实上,通常,应用程序往往包含依赖关系树(依赖关系)在B和C上,B依赖于D,C依赖于E,D依赖于F和G等等,具有超过2个级别,因此SOLID违规更加痛苦,因为必须更改代码以执行某些任务 - 但在此之前,您能想象创建整个应用程序的代码吗? 事实上,类可以有许多自己的依赖 - 在这种情况下,前面描述的问题会成倍增加。
这通常是Bootstrapper的工作 - 它定义了依赖结构,并执行(通常)单个解决方案,使整个系统启动,就像字符串上的木偶一样。
考虑以下情况:依赖于类B和C,B和C的A类都依赖于D类,并期望使用相同的D实例。通常的做法是使D成为单身,但是可能会导致很多问题。另一个选择是将D的实例传递给A的构造函数,并让它创建B和C,或者将B和C的实例传递给A并在外部创建它们 - 并且复杂性一直在继续。
您的代码假定' ProductsController'可以看到' ProductRepository' (组件方式)。如果它们之间没有参考怎么办?大会地图可以是非平凡的。通常,引导代码(我假设它代码中的代码而不是配置文件中的第二个代码)写在引用整个解决方案的程序集中。 (这也是@Steven所描述的。)
单身人士变得轻松(团结:只需使用' containercontrolledlifetimemanager'注册时), Lazy Instantiation非常简单(使用unity:在构造函数中注册映射和询问Func)。 这些只是IoC容器为您提供(几乎)免费的几个例子。
HTH!
答案 2 :(得分:2)
当然你可以这样做,但这会导致以下问题:
IProductRepository
的依赖性不再明确,它看起来像一个可选的依赖IProductRepository
的不同实现,在这种情况下可能会出现问题ProductsController
紧密耦合
在我看来,这不是一个关于框架的问题。关键是通过在构造函数或属性中公开它们的依赖项来使模块可组合。你的例子有点混淆了。
答案 3 :(得分:1)
如果类ProductRepository
未在与ProductsController
相同的程序集中定义(或者如果您希望将其移动到其他程序集),那么您刚刚引入了一个依赖项,而不是#39} ;想要。
这是一种反模式描述为" Bastard Injection"在开创性的着作" .Net"中的依赖注入作者:Mark Seeman。
但是,如果ProductRepository
始终与ProductsController
位于同一个程序集中,并且如果它不依赖于ProductsController
程序集的其余部分所依赖的任何内容,则可能是一个local default
- 在这种情况下它会没问题。
从班级名称来看,我打赌不应该引入这种依赖,而你正在考虑混蛋注射。
答案 4 :(得分:0)
此处ProductsController
负责创建ProductRepository
。
如果ProductRepository
在其构造函数中需要其他参数,会发生什么?然后ProductsController
必须更改,这违反了SRP。
同时为所有对象增加更多复杂性。
除了不清楚调用者是否需要传递子对象,还是可选的?
答案 5 :(得分:0)
主要目的是将对象创建与其使用或消耗分离。对象的创建"通常"由工厂班级负责。在您的情况下,工厂类将被设计为返回实现IProductRepository
接口的类型的对象。
在某些框架中,例如在Sprint.Net中,工厂类实例化在配置中声明性地写入的对象(即在app.config或web.config中)。从而使程序完全独立于它需要创建的对象。这有时非常强大。
答案 6 :(得分:0)
区分依赖注入和控制反转是不一样的。您可以使用依赖注入而不使用IOC框架,例如unity,ninject ...,手动执行注入,他们通常称为穷人的DI。
在我的博客中,我最近写了一篇关于这个问题的帖子 http://xurxodeveloper.blogspot.com.es/2014/09/Inyeccion-de-Dependencias-DI.html
回到你的例子,我看到了实施中的弱点。
1 - ProductsController依赖于具体而非抽象,违反SOLID
2 - 如果接口和存储库存在于不同的项目中,您将被迫引用存储库所在的项目
3 - 如果将来你需要在构造函数中添加一个参数,当它只是一个简单的存储库客户端时,你必须修改它。
4 - 可以为不同的程序员开发控制器和存储库,控制器程序员必须知道如何创建存储库
答案 7 :(得分:0)
考虑这个用例:
以后,如果您想将CustomProductRepository
而不是ProductRepository
注入ProductsController
,请注意已部署到客户端网站的软件。
CustomProductRepository
。因此,您可以避免在客户端站点上重新编译和重新安装软件,因为您尚未修改任何代码。