依赖注入选择

时间:2008-12-11 12:06:32

标签: c# dependency-injection

我正在查看依赖注入,我可以看到它的好处,但我遇到了它创建的语法问题。我有这个例子

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#编码,但欢迎使用其他语言的替代方案

11 个答案:

答案 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。