依赖容器:如何实例化对象实例

时间:2011-03-17 17:36:35

标签: c# .net design-patterns dependency-injection unity-container

我正在使用Unity作为依赖注入引擎。它包含“存储库”和“管理器”类的接口/类。这些存储库负责从/向DB中获取/保存/更新数据,以及管理者“了解”与其他组件/类的对象关系。

实现工厂以获取依赖容器的实例(每个请求1个实例)。当实例化任何存储库/管理器类时,它接收依赖性容器作为构造函数参数,并且可以在需要时创建所有其他管理器/存储库。

有两种方法可以创建业务实体:

  1. 当从数据库存储库中提取数据时,创建对象的实例并使用适当的DB数据初始化它们,其中“容器”字段被初始化。
  2. 当在代码中创建对象以便将其放入DB中时,它接受“IUnityContainer”参数。
  3. 因此,在这两种情况下,任何业务对象都有内部依赖容器,如果他需要获取任何数据,他可以获得适当管理器的实例并请求所需数据。

    问题陈述:上周我读了一些关于SO的问题/答案,说明了这样的事情:

    1. 对象实例不应具有对依赖性容器的访问权限;
    2. 相反,他们应该在构造函数中接收所有必需的接口。
    3. 猜猜,同样适用于管理器/存储库类:我不应该将容器实例传递给它们的构造函数,而应该将接口放到其他必需的组件中。

      对我而言似乎是合理的,但是:

      • 我需要任何对象提供他需要的所有接口(通常,每个经理/存储库/实体需要3-5个);

      • 很多情况下只需要1-2个接口(所以我不想创建很多其他接口);

      问题1 :“隐藏”容器是否真的很好?为什么? (实际上,我觉得我知道为什么,但如果你有好的答案,请告知)。

      问题2 :解决此类问题的良好做法是什么?

      问题3 :在我的特定实现中,当我的对象从DB中提取时(我使用Linq2Sql,并考虑切换到EF)我无法通过DependencyContainer创建对象,我应该自己做(因为对象是使用在SQL站点上执行的表达式创建的,所以将调用无参数构造函数,并将以下数据分配给公共属性;并且SQL端不提供依赖性容器)。有没有解决办法?

      我的实施技术细节:

      Manager基类构造函数的示例:

      public abstract class ManagerBase : IManager
      {
          protected ManagerBase(IUnityContainer container)
          {
              DependancyContainer = container;
          }
      
          protected readonly IUnityContainer DependancyContainer;
      
          ...
      }
      

      存储库基类示例:

      public abstract class RepositoryBase<T, TDb> : IRepository<T>
          where T : IEntity
          where TDb : class, IDbEntity, new()
      {
          protected abstract ITable<TDb> GetTable();
      
          public IQueryable<T> GetAll()
          {
              return GetTable().Select(GetConverter());
          }
      

      如何从DB中提取数据的示例:repository创建对象的实例并使用适当的DB数据初始化它们,其中“Container”字段已初始化:

      public class CountryRepository
          : RepositoryBase<ICountry, DbData.Country>, ICountryRepository
      {   
          protected override Expression<Func<DbData.Country, ICountry>> GetConverter()
          {
              return dbEntity => new Country
              {
                  DependancyContainer = DependancyContainer,
      
                  Code = dbEntity.CountryCode,
                  Name = dbEntity.CountryName,
              };
          }
      

      当需要从DB获取数据时调用以下:

      public class CountryManager : ManagerBase
      {
          ICountry GetCountryById(int countryId)
          {
              ICountryRepository repository = DependancyContainer.Resolve<ICountryRepository>();
              return repository.GetAll()
                  .Where(country=>country.Id==countryId)
                  .SingleOrDefault()
                  ;
          }
      }
      

1 个答案:

答案 0 :(得分:2)

Q1:其他人可能不同意,但通常您不应将DI容器提供给您的域模型/实体类。我发现有两个主要原因:

  1. 将容器赋予实体类会混淆实体类的依赖关系。如果您依赖构造函数注入,您可以通过查看构造函数来查看其依赖项。如果您依赖属性注入,您可以通过查看属性来查看它的依赖关系,尽管这比构造函数注入要清楚(即一些属性是实体类的数据,一些属性是其他属性的接口)服务即实体类;在分离两个类别时存在一些认知负荷)。如果您将容器提供给实体,那么依赖关系将分散在整个类中。
  2. 更容易测试。如果将容器传递给实体类,那么为了测试它,您将不得不模拟/存储容器以及它从容器中检索的接口。您总是可以设置一个轻量级容器,但这比直接提供模拟/存根接口还要多。
  3. Q2 :有人会说好的做法(在DDD下,存储库模式/强域模型模式来自)首先不会将您的实体类暴露给接口。相反,它们应该包含可以在不依赖于其他接口的情况下实现的任何业务逻辑,而更复杂的逻辑应该在Service类中(用DDD的说法)。

    Q3 :您没有说明您正在使用哪种对象关系映射工具,但是可以通过某种拦截器/方面对实体对象实例化过程进行一些控制 - 面向编程模式。然后,这会使你的代码库更接近依赖于这个特定的O / R映射器及其功能(但是更难以在轨道上改变它,尽管我通常倾向于避免在做出架构选择时担心这一点。)

    有关您的示例代码的更多观察结果可能会或可能不会指示您的代码库的其余部分:

    • 你看过AutoMapper了吗?您可能会发现这是您在CountryRepository.GetConverter()类中执行的手动映射的更好替代方法。
    • 看起来您正在为DbData命名空间中的每个类构建并行实体类,可能是因为您无法将所需的服务插入DbData命名空间类。也许我对 Q2 的回答会引导您走向更好的方向(如果可能的话,依靠服务并扩展DbData类。)
    • 我不确定Manager正在扮演什么角色......理想情况下,所有查询方法都应该在存储库中,这样他们才能真正对数据库执行SELECT ... FROM Country where Id = ?查询而不是返回整个表并通过LINQ在内存中进行查询,毕竟这就是数据库所擅长的! (尽管对应用程序的性能特征进行了有意的缓存优化)。我相信GetCountryById方法本身应该在CountryRepository上。

    希望有所帮助!