【问题标题】:Using IOC Container to make repository layer singleton使用 IOC Container 使存储库层单例
【发布时间】:2016-05-19 20:02:39
【问题描述】:

我公司的 MVC 解决方案使用 IOC 容器将缓存/存储库层注入控制器。这是非常昂贵的,因为目前我们每次创建控制器时都会生成新类(可以达到数千个对象,因为缓存层对象具有对 repo 层的引用 - 并且所有这些类都被创建)。我知道单例模式非常不受欢迎,原因有很多(请参阅Why Singletons are Evil),但是否有任何理由不将 IOC 容器设置为缓存/repo 层对象的单例模式?

谢谢。

【问题讨论】:

    标签: c# singleton ioc-container


    【解决方案1】:

    你把事情搞混了。 Singleton 设计模式与 DI 库使用的 Singleton 生活方式完全不同。

    使用单例模式,您通常会在包含该类的唯一实例的具体类上定义一个公共静态只读字段;此实例由类本身创建,整个应用程序可以访问该只读字段。例如:

    public static class CarEngine
    {
        public static readonly CarEngine Instance = new CarEngine();
    
        // class methods
    }
    

    使用 Singleton 生活方式,您可以指示容器在该容器的生命周期内只创建一个实例并重用它。

    Singleton 设计模式是一个问题,因为它迫使消费者对具体类产生硬依赖(违反依赖倒置原则),并且由于具体类在内部控制着创建,因此很难使用测试期间的虚假实现。除此之外,由于消费者没有需要将该类作为依赖项的构造函数,因此可以有效地使阅读代码、创建测试的人以及可以为您进行对象图分析的工具作为 DI 库隐藏依赖项。 The article 你所指的实际上确实在解释单例设计模式为什么不好方面确实做了非常好的工作

    然而,这篇文章从未真正提到过 Singleton 的生活方式,但由于它谈到了 Singleton 设计模式如何隐藏依赖项,这意味着应该注入依赖项。而且由于您希望某些类具有一个实例并通过构造函数注入它们,因此单例生活方式是解决此问题的实际解决方案。

    Singleton 生活方式解决了这些问题,因为您将创建单个实例的责任从具体类转移,这允许消费者在其构造函数中依赖抽象而不是,这使得依赖可见并且代码更可测试。

    因此,将您的注册设为单例并没有错。事实上,我认为您应该更喜欢进行尽可能多的单例注册,因为这可以防止开发人员在练习依赖注入时通常面临的大量问题。通过使每个组件不可变和无状态,它们变得更容易推理,并且您可以防止自己意外地将运行时数据注入到组件中,即bad practice。 DI 的另一个常见陷阱是Captive Dependencies,这意味着一个组件依赖于另一个应该具有较短生命周期的组件。如果你让所有组件不可变、无状态和单例,Captive Dependencies 的问题就消失了,因为单例组件可以安全地相互依赖。

    当然,您的组件中总是需要运行时数据(例如请求数据、O/RM 上下文等),但可以通过将提供程序或简单的Func<DbContext> 注入到适配器实现中来在运行时请求这些数据从您的应用程序中抽象出第三方工具(如果您遵循 SOLID,这是一个很好的做法)。这个 Stackoverflow answer 对此进行了更详细的介绍。

    【讨论】:

      【解决方案2】:

      首先。 .NET 可以在很短的时间内创建数百万个对象。使用 IoC 有点慢,但没那么慢。 benchmark 测试了几个 IoC,它们在几秒钟内解析了 500 000 个对象。

      我公司的 MVC 解决方案使用 IOC 容器将缓存/存储库层注入控制器。这是非常昂贵的,因为目前我们每次创建控制器时都会生成新类(可以达到数千个对象,因为缓存层对象具有对 repo 层的引用 - 并且所有这些类都会被创建)。

      你是说每次请求都会重新生成缓存吗?那么你做错了什么。

      缓存通常是单个实例(即由容器创建一次,然后每次解析缓存时返回相同的实例)。

      存储库必须针对每个请求,因为它需要数据库连接和/或事务。而你想保留这些短暂的生命。

      所以问题不在于 IoC,而在于您如何设计缓存、存储库和缓存对象之间的交互。

      抱歉没有解释清楚。我们将 dapper 用于 Repo 层,因此 Repo 类仅在调用方法时创建与数据库的连接。缓存使用 HTTPCache 和 Redis,但我们有处理逻辑的缓存 Repo 类(如果在缓存中..)如果每个用户调用都创建了数千个重复对象,这似乎会不必要地占用服务器资源

      我还没和你在一起。恕我直言,缓存实体是持久层中的一个实现细节。

      1. 您的存储库是作用域(每个 http 请求创建一个对象)或瞬态(每次新对象)
      2. 您的缓存是 SingleInstance(根据应用程序生命周期在对象上创建)
      3. 您的存储库包含对缓存的引用(构造函数注入)
      4. 当您的存储库无法在缓存中找到对象时,它会查询数据库

      为什么只在存储库中使用缓存(而不是作为持久性实现细节)?因为它降低了复杂性,因为存储库是系统中唯一知道所有实体修改而无需额外逻辑的类。

      【讨论】:

      • 抱歉没有解释清楚。我们将 dapper 用于 Repo 层,因此 Repo 类仅在调用方法时创建与数据库的连接。缓存使用的是 HTTPCache 和 Redis,但我们有处理逻辑的缓存 Repo 类(如果在缓存中)。如果每个用户调用都创建了数千个重复的对象,这似乎会对服务器资源造成不必要的负担
      猜你喜欢
      • 2011-02-06
      • 1970-01-01
      • 2013-12-14
      • 2011-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多