Шаблон для введения типа в виде нескольких дискретных экземпляров-одиночек - PullRequest
0 голосов
/ 06 июля 2019

Я использую ядро ​​asp.net 2.2, у меня есть тип, который должен быть одиночным для предполагаемого варианта использования , однако мне требуется несколько дискретных экземпляров синглтона того же самого введите так, чтобы они могли быть однозначно идентифицированы и вводиться там, где это необходимо.

Другими словами, для прецедента A должен использоваться один синглтон, когда требуется функциональность, связанная с прецедентом A . Для варианта использования n один синглтон должен использоваться, когда требуется любая функциональность, связанная с вариантом использования n .

Синглтон семантически не является одноэлементным в домене приложения, он является синглтоном во всех отдельных случаях использования.

Наивным подходом было бы реорганизовать интерфейсы, чтобы можно было использовать следующий шаблон:

using Microsoft.Extensions.DependencyInjection;

class Program
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<ITypeA, MySingleton>();
        services.AddSingleton<ITypeB, MySingleton>();
    }
}

public class MySingleton : ITypeA, ITypeB
{
}

public interface ITypeA : IMySingleton
{
}

public interface ITypeB : IMySingleton
{
}

public interface IMySingleton
{
}

Затем использовать конкретный экземпляр типа singleton:

class Foo
{
    private readonly IMySingleton instance;
    public Foo(ITypeA instance) => this.instance = instance;
}
class Bar
{
    private readonly IMySingleton instance;
    public Bar(ITypeB instance) => this.instance = instance;
}

Однако это не является ни масштабируемым, ни разумным. Какой шаблон существует, который позволил бы мне выполнить вышеизложенное без непрерывного рефакторинга синглтона для получения новых более узких интерфейсов (ITypeA, ITypeB), которые все реализуют реальную функциональность, которая мне нужна (IMySingleton)?

Ответы [ 2 ]

1 голос
/ 06 июля 2019

Какой шаблон существует, который позволил бы мне выполнить вышеизложенное без непрерывного рефакторинга синглтона для получения новых более узких интерфейсов

Заводская модель будет.

Вместо внедрения целевого сервиса, введите Фабрику, которая возвращает экземпляры вашего сервиса. EG

interface IMyService
{
   . . .
}
interface IMyServiceFactory
{
    IMyService GetInstance(string Name);
}
0 голосов
/ 08 июля 2019

Это потребовало создания нескольких дополнительных классов и модульного теста.Тест разрешает службы из контейнера и подтверждает, что они были разрешены и введены в соответствии с техническими требованиями вашего вопроса.Если мы сможем настроить контейнер так, чтобы тест проходил успешно, мы добьемся успеха.

public interface IServiceA
{
    ISharedService SharedService { get; }
}

public interface IServiceB
{
    ISharedService SharedService { get; }
}

public class ServiceA : IServiceA
{
    public ServiceA(ISharedService sharedService)
    {
        SharedService = sharedService;
    }

    public ISharedService SharedService { get; }
}

public class ServiceB : IServiceB
{
    public ServiceB(ISharedService sharedService)
    {
        SharedService = sharedService;
    }

    public ISharedService SharedService { get; }
}

public interface ISharedService { }

public class SharedService : ISharedService { }

Идея состоит в том, что ServiceA и ServiceB оба зависят от ISharedService.Нам нужно разрешить каждый из них несколько раз для проверки:

  • Когда мы разрешаем IServiceA, всегда ли мы получаем один и тот же экземпляр SharedService?
  • Когда мы решаем IServiceB, всегда ли мы получаем один и тот же экземпляр SharedService?
  • Когда мы разрешаем IServiceA и IServiceB, получаем ли мы разные экземпляры SharedService?

Вот схема модульного теста:

public class DiscreteSingletonTests
{
    [TestMethod]
    public void ResolvesDiscreteSingletons()
    {
        var serviceProvider = GetServiceProvider();
        var resolvedA1 = serviceProvider.GetService<IServiceA>();
        var resolvedA2 = serviceProvider.GetService<IServiceA>();
        var resolvedB1 = serviceProvider.GetService<IServiceB>();
        var resolvedB2 = serviceProvider.GetService<IServiceB>();

        // Make sure we're resolving multiple instances of each. 
        // That way we'll know that the "discrete" singleton is really working.
        Assert.AreNotSame(resolvedA1, resolvedA2);
        Assert.AreNotSame(resolvedB1, resolvedB2);

        // Make sure that all instances of ServiceA or ServiceB receive
        // the same instance of SharedService.
        Assert.AreSame(resolvedA1.SharedService, resolvedA2.SharedService);
        Assert.AreSame(resolvedB1.SharedService, resolvedB2.SharedService);

        // ServiceA and ServiceB are not getting the same instance of SharedService.
        Assert.AreNotSame(resolvedA1.SharedService, resolvedB1.SharedService);
    }

    private IServiceProvider GetServiceProvider()
    {
        // This is the important part.
        // What goes here?
        // How can we register our services in such a way
        // that the unit test will pass?
    }
}

Мы не сможем сделать это только с помощью IServiceCollection/IServiceProvider, если мы не сделаем некоторые действительно уродливые вещи, которые я просто не хочу делать.Вместо этого мы можем использовать различные контейнеры IoC, как рекомендовано этой документацией :

Встроенный служебный контейнер предназначен для удовлетворения потребностей платформы и большинства пользовательских приложений.Мы рекомендуем использовать встроенный контейнер, если вам не нужна особая функция, которую он не поддерживает.

Другими словами, если нам нужны все навороты, мы должны использовать другой контейнер.Вот несколько примеров того, как это сделать:


Autofac

В этом решении используется Autofac.Extensions.DependencyInjection .Вы можете изменить его в соответствии с примером, в котором используется класс Startup.

private IServiceProvider GetServiceProvider()
{
    var serviceCollection = new ServiceCollection();
    var builder = new ContainerBuilder();
    builder.Populate(serviceCollection);

    builder.RegisterType<SharedService>().As<ISharedService>()
        .Named<ISharedService>("ForServiceA")
        .SingleInstance();
    builder.RegisterType<SharedService>().As<ISharedService>()
        .Named<ISharedService>("ForServiceB")
        .SingleInstance();
    builder.Register(ctx => new ServiceA(ctx.ResolveNamed<ISharedService>("ForServiceA")))
        .As<IServiceA>();
    builder.Register(ctx => new ServiceB(ctx.ResolveNamed<ISharedService>("ForServiceB")))
        .As<IServiceB>();

    var container = builder.Build();
    return new AutofacServiceProvider(container);
}

Мы регистрируем ISharedService дважды под разными именами, каждое в виде синглтона.Затем при регистрации IServiceA и ServiceB мы указываем имя зарегистрированного компонента для использования.

IServiceA и IServiceB являются временными (не указано, но это значение по умолчанию).


Castle Windsor

В этом решении используется Castle.Windsor.MsDependencyInjection :

private IServiceProvider GetServiceProvider()
{
    var container = new WindsorContainer();
    var serviceCollection = new ServiceCollection();

    container.Register(
        Component.For<ISharedService, SharedService>().Named("ForServiceA"),
        Component.For<ISharedService, SharedService>().Named("ForServiceB"),
        Component.For<IServiceA, ServiceA>()
            .DependsOn(Dependency.OnComponent(typeof(ISharedService), "ForServiceA"))
            .LifestyleTransient(),
        Component.For<IServiceB, ServiceB>()
            .DependsOn(Dependency.OnComponent(typeof(ISharedService), "ForServiceB"))
            .LifestyleTransient()
    );
    return WindsorRegistrationHelper.CreateServiceProvider(container, serviceCollection);
}

Мы регистрируемсяISharedService дважды с разными именами, каждое как одиночка.(Не указано, но это по умолчанию.) Затем, при регистрации IServiceA и ServiceB мы указываем имя зарегистрированного компонента для использования.


В обоих случаях ясоздать ServiceCollection и ничего с ним не делать.Дело в том, что вы все равно можете регистрировать типы напрямую с помощью IServiceCollection, а не через Autofac или Windsor.Так что если вы зарегистрировали это:

serviceCollection.AddTransient<Whatever>();

... вы можете решить Whatever.Добавление другого контейнера не означает, что вам теперь нужно все регистрировать в этом контейнере.

...