可注入的单例服务还不是 new()able .net core

Injectable singleton service yet not new()able .net core

使用 .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()[ 访问此服务的传统方式=13=]

简而言之,我希望 DI 框架使 Foo 成为单例,同时让开发人员在不使用单例模式的情况下通过 new Foo() 实例化它。

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

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,然后在注册时provide a factory function that retrieves the singleton instance IFoo:

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

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

的新实例

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

此方法使您不必使用单例设计模式,这在 Foo 的情况下是一个 Volatile Dependency(很可能是因为您将其隐藏在抽象背后)。