[英]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>
,因此,即使是静态属性也不同。
它们在编译器眼中是不同的类型。 仅仅因为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()
);
SearchableServiceFactory<A, B>
类与
SearchableServiceFactory<X, Y>
。 因此,您正在处理两组不同的静态成员。 特别是,您有两个不同的字典_SearchableLookupServicesRegistry
。
您正在注册其中之一(在SearchableServiceFactory<OrgLookupService, OrgOutputDto>
)。 另一个(在
SearchableServiceFactory<ISearchableLookupService<IBaseOutputDto>, IBaseOutputDto>
) 保持为空,但您正尝试使用它来检索构造函数。 如果在Create
的throw
语句上设置断点并检查_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
本身的类型可以用作键。 (我需要一些反射才能从TSearchableLookupService
提取它。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.