【问题标题】:Injectable singleton service yet not new()able .net core可注入单例服务但不是 new()able .net 核心
【发布时间】:2021-03-21 23:13:15
【问题描述】:

使用 .net-core DI 框架,我将我的 Foo 服务注册为单例:

 services.AddSingleton<IFoo, Foo>();

然而,我不希望任何其他开发人员创建 Foo 服务的新实例:

var storage = new TokenStorage();  // bad practice, not allowed

我知道我可以使用不可访问的构造函数在内部创建 Foo 服务单例,如下所示:

public class Foo : IFoo 
{
   private static _instance;
   
   protected Foo() {}
   
   public static IFoo GetInstance()
   { 
      return _instance ??= new Foo();
   }
}

但我不希望使用传统方式使用 Foo.GetInstance().Bar()

访问此服务

简单来说,我希望 DI 框架使 Foo 成为单例,同时放弃开发人员通过 new Foo() 实例化它,而不使用单例模式。

有什么方法或模式可以实现这一点吗?

【问题讨论】:

  • 我不明白,你已经对DI机制说,你注册的类是一个单例,机制现在会处理它,每次用户注入IFoo,他们都会获取在第一个请求期间创建的相同实例。
  • @IvanKhorin:OP 基本上是在试图阻止new 被直接使用。这是不可能的,AFAIK。您需要对类型的可访问性才能使用该类型,但任何破坏类型的构造函数或工厂方法的尝试都会将 DI 框架与该类型紧密绑定,这对我来说听起来是个坏主意。
  • @RobertHarvey 是的。 DI 应该只关心注入,但我想要一个不可更新的类型,不管有人使用 DI 与否。我自己认为这是不可能的,但我很担心,特别是在驱动硬件时。我总是使用 GetInstance()
  • 你为什么不简单地注入单例?喜欢services.AddSingleton&lt;IFoo&gt;(() =&gt; Foo.GetInstance());?
  • @canton7 谢谢。实施是为了简洁。还有很多其他的事情需要考虑:c-sharpcorner.com/UploadFile/8911c4/….

标签: c# .net-core design-patterns dependency-injection singleton


【解决方案1】:

Foo 的实现移动到包含您的Composition Root 的项目中。由于该项目已经依赖于应用程序中的所有其他项目,因此(不手动破解项目文件)让这些项目引用启动项目是不可能的(因为这会导致循环依赖)。这有效地将Foo 隐藏在所有其他项目中。这些项目只能依赖于IFoo 抽象,它们永远不会意外创建Foo 的新实例。

如果Foo 包含太多无法移动的逻辑,或者,您可以使Foo 抽象并在Composition Root 中创建FooImpl 派生并注册它。效果是一样的;其他项目中的代码无法创建FooImpl,因为它们没有对启动项目的引用。

这种方法使您不必使用单例设计模式,这在FooVolatile Dependency 的情况下存在问题(很可能是因为您将其隐藏在抽象后面)。

【讨论】:

    【解决方案2】:

    使用单例模式实现Foo,然后在注册IFoo时实现provide a factory function that retrieves the singleton instance

    services.AddSingleton<IFoo, Foo>(_ => Foo.GetInstance());
    

    【讨论】:

      【解决方案3】:
      using Microsoft.Extensions.DependencyInjection;
      using System;
      
      namespace ConsoleApp1
      {
          interface IFoo { int FooProp { get; } }
      
          interface IBar { int BarProp { get; } }
      
          class Foo : IFoo { public int FooProp => 10; }
      
          class Bar : IBar { public int BarProp => 10; }
      
          class Program
          {
              static readonly ServiceProvider _serviceProvider = ConfigureServices().BuildServiceProvider();
      
              static IServiceCollection ConfigureServices() =>
                  new ServiceCollection()
                      .AddSingleton<IFoo, Foo>()
                      .AddTransient<IBar, Bar>();
      
              static void Main(string[] args)
              {
                  ConfigureServices();
      
                  var foo1 = _serviceProvider.GetRequiredService<IFoo>();
                  var foo2 = _serviceProvider.GetRequiredService<IFoo>();
      
                  var bar1 = _serviceProvider.GetRequiredService<IBar>();
                  var bar2 = _serviceProvider.GetRequiredService<IBar>();
      
                  Console.WriteLine($"foo1 & foo2 are {(foo1 == foo2 ? "same" : "different")} instances.");
                  Console.WriteLine($"bar1 & bar2 are {(bar1 == bar2 ? "same" : "different")} instances.");
              }
          }
      }
      

      结果: foo1 & foo2 是相同的实例。 bar1 和 bar2 是不同的实例。

      【讨论】:

      • 如果有人不知道注入和解决所需的服务怎么办? Foo 不应该在项目范围内实例化,并且没有人应该能够使用 new Foo() 手动执行此操作
      • @Efe 那么他们可能不应该修改你的代码库:)
      • @MathiasR.Jessen repo forkers 总是在那里,就像与应用程序搏斗的客户一样 :)
      • @Efe,你为什么关心别人的叉子?是你的头疼吗?射腿是他们的选择:)
      • @IvanKhorin 当你定义一个限制时,它应该是一个限制。规则是其他的。
      猜你喜欢
      • 2020-11-05
      • 2018-05-08
      • 2020-01-17
      • 1970-01-01
      • 2019-01-31
      • 2019-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多