我正在查看依赖注入,我可以看到它的好处,但我遇到了它创建的语法问题。我有这个例子
public class BusinessProducts
{
IDataContext _dx;
BusinessProducts(IDataContext dx)
{
_dx = dx;
}
public List<Product> GetProducts()
{
return dx.GetProducts();
}
}
问题在于我不想写
BusinessProducts bp = new BusinessProducts(dataContextImplementation);
我会继续写
BusinessProducts bp = new BusinessProducts();
因为我觉得第一种选择只是感觉不自然。我不想知道BusinessProduct“依赖”什么来获取产品,我也觉得它使我的代码更难以理解。
这种方法是否有其他替代方法,因为我希望保留原始语法来创建对象,但我仍希望能够在单元测试时伪造依赖关系,还是这种依赖注入框架可以为我做什么? / p>
我在c#编码,但欢迎使用其他语言的替代方案
答案 0 :(得分:9)
我为我的上下文使用工厂并注入它,如果提供的工厂为null,则提供合适的默认值。我这样做有两个原因。首先,我使用数据上下文作为工作范围对象的单元,因此我需要能够在需要时创建它们,而不是保持一个。其次,我主要使用DI来提高可测试性,只能将二次考虑解耦。
所以我的商业产品类看起来像:
public class BusinessProducts
{
private IDataContextFactory DataContextFactory { get; set; } // my interface
public BusinessProducts() : this(null) {}
public BusinessProducts( IDataContextFactory factory )
{
this.DataContext = factory ?? new BusinessProductsDataContextFactory();
}
public void DoSomething()
{
using (DataContext dc = this.DataContextFactory().CreateDataContext())
{
...
}
}
另一种方法是使工厂属性可公开设置,并通过设置属性注入备用工厂。无论哪种方式,如果你想保留null构造函数,你需要提供一个默认值。
答案 1 :(得分:6)
您可以创建工厂。 DI容器最适合在设置时发生的布线 - 而不是在运行时(因为这看起来是这样)。工厂可以以不同的方式实施,具体取决于它可以插入的程度,以及需要使用它的地方。
答案 2 :(得分:6)
我通常会有一个空构造函数,它使用一个实体实例(或由IoC创建的实例),并使用DI。即
public class BusinessProducts
{
IDataContext _dx;
BusinessProducts()
{
_dx = new SolidDataContext();
}
BusinessProducts(IDataContext dx)
{
_dx = dx;
}
}
这样,您可以使用DI来覆盖单元测试测试中的默认实例。
答案 3 :(得分:4)
你的感情虽然有效,却是错位的。
Dependency Injection模式是Inversion of Control原则的直接应用。
这意味着,不是您的类控制它消耗的其他类的实例,而是反转并且为其提供依赖关系。
因此,您的类通过构造函数参数或属性自然地公开它们的依赖项。对这种结构表示不屑,说你还没有真正理解这种模式。
答案 4 :(得分:3)
这里有两个不同的案例:
在生产代码中,您永远不会写
new BusinessProducts(dataContextImplementation)
因为依赖注入通常会为您创建完整的对象层次结构。这是依赖注入模式的“病毒”性质,它们倾向于完全控制您的服务创建。
在单元测试代码中,您通常会自己创建它,但通常您将提供模拟对象或dataContextImplementation的存根实现。所以通常你会注入一个没有大量后续依赖项的对象。
答案 5 :(得分:1)
http://springframework.net/和http://structuremap.sourceforge.net/Default.htm可能是基于.NET的语言最常用的DI框架,并且都可以满足您的需求。
答案 6 :(得分:1)
通常,框架本身将具有构建整个对象树的逻辑。例如,而不是
new SomeObjectO(diContext)
你会像这样调用框架:
DIFramework.GetNew<SomeObjectO>();
或
DIFramework.Get<SomeObject>();
另一个有趣的框架,看看你是否想要了解DI和过程是微软的Unity和Object Builder项目。
答案 7 :(得分:1)
如果您真的不喜欢在构造函数中注入此实例,则可以尝试将CommonServiceLocator与您最喜欢的兼容.NET依赖注入框架一起使用。这将允许您编写如下代码:
public class BusinessProducts
{
IDataContext _dx;
BusinessProducts()
{
_dx = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<IDataContext>();
}
public List<Product> GetProducts()
{
return dx.GetProducts();
}
}
但是,请注意,当他们知道您使用依赖注入框架时,这并不是大多数人所期望的。我认为使用依赖注入框架并让它为你创建所有对象更为常见。
BusinessProducts bp = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<BusinessProducts>();
如果您想避免使用依赖注入框架路径,那么使用工厂可能是最好的方法。
答案 8 :(得分:1)
有一种叫穷人的DI的技术看起来像这样
public class BusinessProducts
{
IDataContext _dx;
BusinessProducts() : this(new DataContext()) {}
BusinessProducts(IDataContext dx)
{
_dx = dx;
}
public List<Product> GetProducts()
{
return dx.GetProducts();
}
}
这并不理想,因为它将您与实现联系起来,但它是解耦代码的良好基础。这与@tvanfosson类似,但更为简单。
我是温莎推荐的第二个
答案 9 :(得分:1)
我的代码将引用Microsoft Unity但我相信它非常适用于所有DI框架。如果你正确使用DI,你永远不需要调用新的BusinessObject(新的dataContext),DI协会将为你处理它。
我的例子有点长,因为我将粘贴一些代码,用于运行完全由DI加载的Model View Presenter网站。 (如果您希望完整的源代码检查我的博客并从我的Assembla SVN服务器下载它)
加载容器(可以是我喜欢的代码或使用配置)
protected void Application_Start(object sender, EventArgs e)
{
Application.GetContainer()
// presenters / controllers are per request
.RegisterType<IEmployeeController, EmployeeController>(new ContextLifetimeManager<IEmployeeController>())
//Data Providers are Per session
.RegisterType<IEmployeeDataProvider, EmployeeDataProvider>(new SessionLifetimeManager<IEmployeeDataProvider>())
//Session Factory is life time
.RegisterType<INHibernateSessionManager, NHibernateSessionManager>(new ContainerControlledLifetimeManager());
}
自定义HTTP模块在OnPreRequest调用期间为每个页面调用Unity BuildUp Method。
private static void OnPreRequestHandlerExecute(object sender, EventArgs e)
{
var handler = HttpContext.Current.Handler;
HttpContext.Current.Application.GetContainer().BuildUp(handler.GetType(), handler);
// User Controls are ready to be built up after the page initialization is complete
var page = HttpContext.Current.Handler as Page;
if (page != null)
{
page.InitComplete += OnPageInitComplete;
}
}
使用[Dependency]属性
修饰的页面容器展示器public partial class Employees : Page, IEmployeeView
{
private EmployeePresenter _presenter;
[Dependency]
public EmployeePresenter Presenter
{
set
{
_presenter = value;
_presenter.View = this;
}
}
}
Presenter with InjectionConstructor方法
public class EmployeePresenter : Presenter<IEmployeeView>
{
private readonly IEmployeeController _controller;
[InjectionConstructor]
}
public EmployeePresenter(IEmployeeController controller)
{
_controller = controller;
}
控制器紧随其后
public class EmployeeController : IEmployeeController
{
private readonly IEmployeeDataProvider _provider;
[InjectionConstructor]
public EmployeeController(IEmployeeDataProvider DataProvider)
{
_provider = DataProvider;
}
}
与提供者相同
public class EmployeeController : IEmployeeController
{
private readonly IEmployeeDataProvider _provider;
[InjectionConstructor]
public EmployeeController(IEmployeeDataProvider DataProvider)
{
_provider = DataProvider;
}
}
最后是会话管理器,它只包含一个常规构造函数。
public class NHibernateSessionManager : INHibernateSessionManager
{
private readonly ISessionFactory _sessionFactory;
public NHibernateSessionManager()
{
_sessionFactory = GetSessionFactory();
}
}
那么当页面请求启动时会发生什么,HttpModule会在页面上调用BuildUp()方法。然后Unity看到标有Dependency属性的属性,并检查它的容器,看它内部是否存在EmployeePresenter对象。
由于容器中没有这样的对象,因此它将尝试创建EmployeePresenter。在检查创建它在Presenter中看到的类时,它需要一个需要注入IEmployeeController的构造函数。由于容器实际上有一个控制器管理器,它将查看容器的实例是否存在于页面请求开头不存在的容器中,因此它将实例化控制器。
然后Unity会看到控制器需要注入一个IEmployeeDataProvider,它将继续执行此过程,直到它最终到达Provider需要注入会话管理器的点。由于会话管理器不再需要注入,因此Unity将创建会话管理器的实例,将其存储在容器中,用于给定的ContainerLifeTimeManager,将其注入Provider并存储该实例,依此类推,直至创建完成的位置。页面的EmployeePresenter依赖项。
答案 10 :(得分:0)
您也可以查看windsor获取IoC。