[英]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.