繁体   English   中英

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

[英]Factory Pattern with Generic Type Constraints

我正在实施一种工厂模式,并在 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# 问题)。

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()
);

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

您正在注册其中之一(在SearchableServiceFactory<OrgLookupService, OrgOutputDto> )。 另一个(在
SearchableServiceFactory<ISearchableLookupService<IBaseOutputDto>, IBaseOutputDto> ) 保持为空,但您正尝试使用它来检索构造函数。 如果在Createthrow语句上设置断点并检查_SearchableLookupServicesRegistry ,您将看到其Count0

泛型的问题在于它们似乎提供了一些动态行为,但实际上并没有。 所有泛型类型参数都是在编译时确定的。 使用泛型的复杂场景往往会变得非常复杂。 如果你需要高度动态,你有时必须放弃完全类型安全。

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

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本身的类型可以用作键。 (我需要一些反射才能从TSearchableLookupService提取它。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM