簡體   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