正确的DI或服务定位器反模式?

时间:2019-06-20 16:42:12

标签: c# dependency-injection .net-core

我正在开发一个SDK,并且对我的类及其依赖项使用DI(我认为不正确)。我相信我将DI和服务定位器混在一起了。我的问题是:

  • 知道在哪里构建我的DI容器。我没有用作composition root的运行时/入口点-也不应该根据该帖子。
  • 服务寿命。我创建DI容器的方式导致创建多个单例对象,并且每个类都有自己的DI容器-我绝对想避免

DI via Builder equivalent to service locator anti-pattern?似乎是我所追求的,但未得到解答。

我使用.NET DI定义容器:

public class ServiceProvider
{
        private Microsoft.Extensions.DependencyInjection.ServiceProvider serviceProvider;
        protected readonly IServiceCollection serviceCollection = new ServiceCollection();
        public ServiceProvider()
        {
            serviceCollection.AddScoped<IScopedService, ScopedService>();
            serviceCollection.AddSingleton<ISingletonService, SingletonService>();
        }
}

现在,当ScopedService依赖SingletonService时,可以通过适当的构造函数注入来完成:

public ScopedService(ISingletonService singletonService)
{
    _singletonService = singletonService
}

但是,消费类(最终用户)是通过服务定位器完成的,例如

private IScopedService _scopedService;
private IScopedService ScopedService
{
    get
    {
        return _scopedService ?? _scopedService = serviceProvider.GetService<IScopedService>();
    }
}

我的所有消耗类都继承自基类,我相信每次实例化一个类时,基类都会创建ServiceProvider的新实例。

    public abstract class ComponentBase
    {
        protected internal ServiceProvider serviceProvider = new ServiceProvider();
    }

问题:正在创建多个Singleton对象

            var singleton1 = consumingClass.serviceProvider.GetService<ISingletonService>();
            var singleton2 = consumingClass.serviceProvider.GetService<ISingletonService>();

Assert.AreEqual(singleton1, singleton2) // Test failure

我希望Singleton服务是同一实例。我应该将基类中的ServiceProvider设为静态吗?这将解决此问题,但在服务定位器反模式的方向上似乎还有更多趋势。

我应该转向工厂模式吗?我在服务提供商中注册所有使用类的地方,只有工厂知道DI容器?然后您可以通过在工厂中调用吸气剂来上课?

1 个答案:

答案 0 :(得分:2)

我建议不要在SDK中使用自己的IServiceProvider,类似于链接文章中所述。

相反,公开SDK的扩展点以插入使用者的DI

public static MySDKServiceCollectionExtensions {
    public static IServiceCollection AddMySDK(this IServiceCollection services) {
        services.AddScoped<IScopedService, ScopedService>();
        services.AddSingleton<ISingletonService, SingletonService>();

        //...additional services.

        return services
    }
}

如果您的使用方组件需要访问这些服务,则通过构造函数注入显式使用依赖原则

public abstract class ComponentBase {        
    protected ISingletonService singleton;

    public ComponentBase(ISingletonService singleton) {
        this.singleton = singleton;
    }
}

SDK中的IServiceProvider被视为实现问题,与您的SDK无关。

  

DI容器仅应从成分根引用。所有其他模块都不应引用该容器。

使用中的项目/应用程序(最终用户)会将您的SDK添加到其合成根目录中

//...

IServiceCollection serviceCollection = new ServiceCollection();

//...Add SDK
serviceCollection.AddMySDK();

//Add my services.
serviceCollection.AddScoped<IConsumer, Consumer>()

//...

并根据需要使用它

public class Consumer: IConsumer {
    protected internal IScopedService service;

    protected Consumer(IScopedService service) {
        this.service = service
    }

    private IScopedService ScopedService {
        get {
            return service;
        }
    }

    //...
}