14

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

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 “依赖”什么来获得产品,而且我觉得它使我的代码更难读。

这种方法是否有任何替代方法,因为我想保留用于创建对象的原始语法,但我希望在单元测试时仍然能够伪造依赖关系,或者这种依赖注入框架可以为我做些什么?

我正在用 c# 编码,但欢迎使用其他语言的替代品

4

11 回答 11

10

我为我的上下文使用工厂并注入它,如果提供的工厂为空,则提供合适的默认值。我这样做有两个原因。首先,我将数据上下文用作工作单元范围的对象,因此我需要能够在需要时创建它们,而不是保留一个。其次,我主要使用 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 构造函数,都需要提供默认值。

于 2008-12-11T12:32:26.703 回答
6

您可以创建一个工厂。DI 容器最适合在设置时发生的接线 - 而不是在运行时(看起来就是这种情况)。工厂可以以不同的方式实现,具体取决于它需要的可插拔性以及您需要使用它的位置。

于 2008-12-11T12:18:44.470 回答
6

我通常会有一个空的构造函数,它使用一个实体实例(或由 IoC 创建的实例),以及一个带有 DI 的构造函数。IE

public class BusinessProducts
{
   IDataContext _dx;

   BusinessProducts()
   {
      _dx = new SolidDataContext();
   }

   BusinessProducts(IDataContext dx)
   {
      _dx = dx;
   }
}

这样,您可以使用 DI 在单元测试测试中覆盖默认实例。

于 2008-12-11T12:36:10.207 回答
4

你的感觉虽然有效,但却是错误的。

依赖注入模式是控制反转原则的直接应用。

这意味着,不是您的类控制它使用的其他类的实例,而是反转关系并为其提供依赖关系。

因此,您的类自然会通过构造函数参数或属性公开它们的依赖关系。对这种结构表现出不屑一顾表示你还没有真正理解这种模式。

于 2008-12-11T15:17:41.333 回答
3

这里有两种不同的情况:

在生产代码中你永远不会

new BusinessProducts(dataContextImplementation)

因为依赖注入通常会为您创建完整的对象层次结构。这是依赖注入模式的“病毒”性质,它们倾向于完全控制您的服务创建。

在单元测试代码中,您通常会自己创建它,但通常您会提供一个模拟对象或 dataContextImplementation 的存根实现。所以通常你会注入一个没有大量后续依赖的对象。

于 2008-12-11T12:15:25.287 回答
1

http://springframework.net/http://structuremap.sourceforge.net/Default.htm可能是最常用的基于 .NET 的语言的 DI 框架,它们都可以满足您的需求。

于 2008-12-11T12:10:37.030 回答
1

通常,框架本身将具有构建整个对象树的逻辑。例如,而不是

new SomeObjectO(diContext)

你可以这样调用框架:

DIFramework.GetNew<SomeObjectO>();

或者

DIFramework.Get<SomeObject>();

如果您想了解 DI 和流程,可以看看另一个有趣的框架是 Microsoft 的 Unity 和 Object Builder 项目。

于 2008-12-11T12:28:37.210 回答
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>();

如果您想避免依赖注入框架路径,使用工厂可能是最好的方法。

于 2008-12-11T12:55:31.527 回答
1

有一种技术叫做穷人的 DI,看起来像这样

public class BusinessProducts
{
   IDataContext _dx;

   BusinessProducts() : this(new DataContext()) {}

   BusinessProducts(IDataContext dx)
   {
      _dx = dx;
   }

   public List<Product> GetProducts()
   {
    return dx.GetProducts();
   }
}

这并不理想,因为它将您与实现联系在一起,但它是实现解耦代码的良好垫脚石。这类似于@tvanfosson,但要简单得多。

我赞成温莎的建议

于 2008-12-11T14:07:32.550 回答
1

我的代码将引用 Microsoft Unity,但我确信它非常适用于所有 DI 框架。如果您正确使用 DI,则无需调用 new BusinessObject(new dataContext),DI 关联将为您处理一切。

我的示例会有点长,因为我将粘贴一些用于运行由 Unity 完全加载的 DI 的模型视图演示器网站的代码。(如果你想要完整的源代码,请查看我的博客并从我的 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 方法。

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;
        }
    }
}

带有 InjectionConstructor 方法的 Presenter

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 属性的 Property,并将检查它的容器以查看其中是否存在 EmployeePresenter 对象。

由于容器中没有这样的对象,因此它将尝试创建一个 EmployeePresenter。在检查创建它在 Presenter 中看到的类时,它需要一个需要将 IEmployeeController 注入其中的构造函数。由于容器实际上有一个控制器的管理器,它将查看容器中是否存在它的实例,而在页面请求的开头不存在,因此它将实例化控制器。

然后,Unity 将看到控制器需要注入 IEmployeeDataProvider,它将继续此过程,直到最终到达 Provider 需要注入会话管理器的地步。由于会话管理器不再需要注入,Unity 将创建会话管理器的一个实例,将其存储在容器中,因为它给定了 ContainerLifeTimeManager,将其注入提供者并存储该实例,依此类推,直到它完成创建一个页面的 EmployeePresenter 依赖项。

于 2008-12-11T14:21:45.423 回答
0

您还可以查看Windsor for IoC 。

于 2008-12-11T12:16:18.780 回答