Правильный DI или сервисный локатор против паттернов? - PullRequest
0 голосов
/ 20 июня 2019

Я разрабатываю SDK и использую DI (я думаю, что неправильно) для своих классов и их зависимостей. Я считаю, что я смешал DI и сервисный локатор Мои проблемы:

  • Зная, где построить мой DI-контейнер. У меня нет среды выполнения / точки входа для использования в качестве корня композиции - и не должен в соответствии с этим постом.
  • Срок службы. То, как я создаю свой DI-контейнер, вызывает создание нескольких одноэлементных объектов, и каждый класс имеет свой собственный DI-контейнер - я определенно хочу избежать этого

Этот DI через Builder эквивалентен шаблону поиска служб? , кажется, то, что я ищу, но без ответа.

Я определяю свой контейнер с помощью .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 Ответ

2 голосов
/ 20 июня 2019

Я бы не советовал иметь свой собственный IServiceProvider в SDK, подобный тому, что было сказано в связанной статье.

Вместо этого предоставьте точку расширения для вашего 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;
    }
}

Наличие IServiceProvider в вашем SDK рассматривается как проблема реализации, которая не должна иметь значения для вашего 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;
        }
    }

    //...
}
...