我使用 Unity 作为依赖注入(inject)引擎。它包含用于“存储库”和“管理器”类型的类的接口(interface)/类。这些存储库负责从/向数据库获取/保存/更新数据,并且管理人员“知道”与其他组件/类的对象关系。

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

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

  • 当从数据库存储库中提取数据时,会创建对象实例并使用适当的数据库数据对其进行初始化,其中“容器”字段将被初始化。
  • 当在代码中创建要放入数据库的对象时,它接受“IUnityContainer”参数。

  • 因此,在这两种情况下,任何业务对象内部都有依赖容器,如果他需要获取任何数据,他可以获取适当管理器的实例并请求所需的数据。

    问题陈述:上周我阅读了一些关于 SO 的问题/答案,这些问题/答案是这样的:
  • 对象实例不应访问依赖容器;
  • 相反,他们应该在构造函数中接收所有必需的接口(interface)。

  • 猜猜,这同样适用于管理器/存储库类:我不应该将容器实例传递给它们的构造函数,而是应该将接口(interface)放置到其他必需的组件中。

    对我来说,这似乎很合理,但是:
  • 我需要任何对象来提供他需要的所有接口(interface)(通常,每个管理器/存储库/实体需要 3-5 个);
  • 很多情况下只需要1-2个接口(interface)(所以我不想创建很多其他的);

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

    问题 2 :解决此类问题的最佳做法是什么?

    问题 3 :在我从 DB 中提取对象的特定实现中(我使用 Linq2Sql,并考虑切换到 EF)我无法通过 DependencyContainer 创建对象,我应该自己做(因为对象是通过使用表达式创建的)在 SQL 站点上执行时,将使用分配给公共(public)属性的以下数据调用无参数构造函数;并且依赖容器在 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());
        }
    

    如何从数据库中提取数据的示例:存储库创建对象实例并使用适当的数据库数据对其进行初始化,其中“容器”字段已初始化:
    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,
            };
        }
    

    当需要从数据库获取数据时调用以下方法:
    public class CountryManager : ManagerBase
    {
        ICountry GetCountryById(int countryId)
        {
            ICountryRepository repository = DependancyContainer.Resolve<ICountryRepository>();
            return repository.GetAll()
                .Where(country=>country.Id==countryId)
                .SingleOrDefault()
                ;
        }
    }
    

    最佳答案

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

  • 将容器交给实体类会混淆实体类的依赖关系。如果您依赖构造函数注入(inject),则可以通过查看构造函数来了解其依赖项是什么。如果你依赖属性注入(inject),你可以通过查看属性来了解它的依赖项是什么,尽管这比构造函数注入(inject)要清晰一些(即有些属性是实体类的数据,有些属性是其他属性的接口(interface))实体类的服务;分离这两个类别有一些认知负担)。如果您向实体提供容器,则依赖项将分散在整个类中。
  • 测试更简单。如果您将容器传递给实体类,那么为了测试它,您必须模拟/ stub 容器以及它从容器中检索的接口(interface)。你总是可以设置一个轻量级容器,但这比直接提供模拟/ stub 接口(interface)仍然需要更多的工作。

  • Q2 :有人会说好的做法(在 DDD 下,存储库模式/强域模型模式来自于此)首先不会将您的实体类暴露给接口(interface)。相反,它们应该包含可以在不依赖其他接口(interface)的情况下实现的任何业务逻辑,并且更复杂的逻辑应该在 Service 类中(用 DDD 的说法)。

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

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

  • 希望有帮助!

    关于c# - 依赖容器 : how to instantiate object instances,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/5342927/

    10-13 08:31
    查看更多