具有通用类型约束的工厂模式

时间:2018-12-31 15:27:07

标签: c# generics factory-pattern

我正在实现一种工厂模式,并在代码审查中找到this neat-looking pattern

我已经实现了该解决方案,其中包括以下几种变化:

我有一个工厂类,看起来像这样:

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 :(得分:1)

SearchableServiceFactory<OrgLookupService, OrgOutputDto>SearchableServiceFactory<ISearchableLookupService<IBaseOutputDto>, IBaseOutputDto>类型不同,因此,甚至静态属性也不同。

在编译器看来,它们是不同的类型。仅仅因为OrglookupServiceISearchableLookupService,并不是每个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()
);

答案 1 :(得分:0)

SearchableServiceFactory<A, B>
类不同  SearchableServiceFactory<X, Y>。因此,您要处理两组不同的静态成员。特别是,您有两个不同的字典_SearchableLookupServicesRegistry

您正在其中一个中注册(在SearchableServiceFactory<OrgLookupService, OrgOutputDto>中)。另一个(在
SearchableServiceFactory<ISearchableLookupService<IBaseOutputDto>, IBaseOutputDto>)保持为空,但您尝试将其用于构造函数的检索。如果您在throw的{​​{1}}语句上设置断点并检查Create,则会看到其_SearchableLookupServicesRegistryCount

泛型的问题在于它们似乎提供了一些动态行为,但它们却没有。确定所有泛型类型参数的编译时间。使用泛型的复杂方案通常会变得非常复杂。如果您需要高度动态化,则有时必须放弃全类型安全性。

这是我对服务工厂的建议:

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中提取出来。)