Заводская модель с ограничениями общего типа - PullRequest
0 голосов
/ 31 декабря 2018

Я реализую тип фабричного шаблона и нашел этот аккуратный шаблон в Code Review.

Я реализовал это решение с некоторыми изменениями следующим образом:

У меня есть фабричный класс, который выглядит так:

public class SearchableServiceFactory<TSearchableLookupService, TOutputDto>
    where TOutputDto : IBaseOutputDto
    where TSearchableLookupService : ISearchableLookupService<TOutputDto>
{
    static readonly Dictionary<string, Func<TSearchableLookupService>> _SearchableLookupServicesRegistry =
        new Dictionary<string, Func<TSearchableLookupService>>();

    private SearchableServiceFactory() { }

    public static TSearchableLookupService Create(string key)
    {
        if (_SearchableLookupServicesRegistry.TryGetValue(
            key, out Func<TSearchableLookupService> searchableServiceConstructor)
        )
            return searchableServiceConstructor();

        throw new NotImplementedException();
    }

    public static void Register<TDerivedSearchableService>
    (
        string key,
        Func<TSearchableLookupService> searchableServiceConstructor
    )
        where TDerivedSearchableService : TSearchableLookupService
    {
        var serviceType = typeof(TDerivedSearchableService);

        if (serviceType.IsInterface || serviceType.IsAbstract)
            throw new NotImplementedException();

        _SearchableLookupServicesRegistry.Add(key, searchableServiceConstructor);
    }

Это работает.Я называю это из кода, таким образом:

...
SearchableServiceFactory<OrgLookupService, OrgOutputDto>.Register<OrgLookupService>
(
    nameof(Organization), () => new OrgLookupService(_Context, _OrganizationRepository)
);
...

Это работает.Конструктор добавляется в словарь вместе с ключом.Затем я иду, чтобы получить этот конструктор по ключу, чтобы получить экземпляр и что-то с ним сделать, например:

SearchableServiceFactory<ISearchableLookupService<IBaseOutputDto>, IBaseOutputDto>.Create(myKey).DoAThing();

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

Я использую .NET Core 2.1, если это имеет значение (это похоже на проблему исключительно в C #).

Ответы [ 2 ]

0 голосов
/ 31 декабря 2018

SearchableServiceFactory<OrgLookupService, OrgOutputDto> не тот же тип, что и SearchableServiceFactory<ISearchableLookupService<IBaseOutputDto>, IBaseOutputDto>, и поэтому даже статические свойства различны.

Это разные типы в глазах компилятора.Просто потому, что OrglookupService является ISearchableLookupService, не каждый ISearchableLookupService является OrglookupService.

Возможный обходной путь - использовать SearchableServiceFactory<ISearchableLookupService<IBaseOutputDto>, IBaseOutputDto> для регистрации вашего объекта, но для этого потребуется ISearchableLookupService быть ковариантным.

public interface ISearchableLookupService<out TOutputDto> 
    where TOutputDto : IBaseOutputDto
{

}

И зарегистрироваться так:

SearchableServiceFactory<ISearchableLookupService<IBaseOutputDto>, IBaseOutputDto>.Register<OrgLookupService>
(
    nameof(Organization), () => new OrgLookupService()
);
0 голосов
/ 31 декабря 2018

Класс SearchableServiceFactory<A, B> не совпадает с классом
SearchableServiceFactory<X, Y>.Следовательно, вы имеете дело с двумя различными наборами статических элементов.В частности, у вас есть два разных словаря _SearchableLookupServicesRegistry.

. Вы регистрируетесь в одном из них (в SearchableServiceFactory<OrgLookupService, OrgOutputDto>).Другой (в
SearchableServiceFactory<ISearchableLookupService<IBaseOutputDto>, IBaseOutputDto>) остается пустым, но вы пытаетесь использовать его для извлечения конструктора.Если вы установите точку останова для оператора throw в Create и осмотрите _SearchableLookupServicesRegistry, вы увидите, что Count равно 0.

Проблема с дженериками заключается в том, что они могут отображатьсяпредлагают некоторое динамическое поведение, но они этого не делают.Все параметры универсального типа определяются временем компиляции.Сложные сценарии, использующие дженерики, часто становятся очень запутанными.Если вам нужна высокая динамичность, иногда вы должны отказаться от полной безопасности типов.

Это мое предложение для фабрики услуг:

public static class SearchableServiceFactory
{
    static readonly Dictionary<string, Func<ISearchableLookupService<IBaseOutputDto>>> 
        _SearchableLookupServicesRegistry =
            new Dictionary<string, Func<ISearchableLookupService<IBaseOutputDto>>>();

    public static TSearchableLookupService Create<TSearchableLookupService>(string key)
        where TSearchableLookupService : ISearchableLookupService<IBaseOutputDto>
    {
        if (_SearchableLookupServicesRegistry.TryGetValue(
            key,
            out Func<ISearchableLookupService<IBaseOutputDto>> searchableServiceConstructor))
        {
            return (TSearchableLookupService)searchableServiceConstructor();
        }

        throw new ArgumentException($"Service for \"{key}\" not registered.");
    }

    public static void Register(
        string key,
        Func<ISearchableLookupService<IBaseOutputDto>> searchableServiceConstructor)
    {
        _SearchableLookupServicesRegistry.Add(key, searchableServiceConstructor);
    }
}

Обратите внимание, что SearchableServiceFactory не является универсальным.Это необходимо для того, чтобы иметь только одну фабрику и, следовательно, только один статический словарь.

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

public interface ISearchableLookupService<out TOutputDto> where TOutputDto : IBaseOutputDto
{
    TOutputDto GetOutputDto();
}

Вы можете зарегистрироваться с помощью

SearchableServiceFactory.Register(
    nameof(Organization), () => new OrgLookupService(_Context, _OrganizationRepository));

и создать с помощью

IBaseOutputDto result = SearchableServiceFactory
            .Create<ISearchableLookupService<IBaseOutputDto>>(nameof(Organization))
            .GetOutputDto();

или для получения более конкретного типа

OrgOutputDto result = SearchableServiceFactory
    .Create<ISearchableLookupService<OrgOutputDto>>(nameof(Organization))
    .GetOutputDto();

Но этот последний пример делает строковый ключ nameof(Organization) избыточным, поскольку сам тип OrgOutputDto может использоваться в качестве ключа.(Мне понадобится Reflection, чтобы извлечь его из TSearchableLookupService.)

...