2013-09-26 26 views
2

我目前停留在試圖編寫不依賴服務位置的工廠類。沒有服務定位器的工廠模式

我能想到的唯一的另一種選擇是使用構造函數注入來注入所有可能的實例,但這可能會導致意外,因爲類是通過引用傳遞的。 一旦可能的供應商數量增長,它也可能會變得昂貴和混亂。

提供者本身是完全複雜的類,它們有自己的依賴關係,因此手工構建不在圖片中。

更新服務位置例如:

public class ProviderFactory : IProviderFactory 
    { 
     private readonly IProviderConfigurationService _providerConfigurationService; 

     public enum SearchType 
     { 
      Foo, 
      Bar 
     } 

     public ProviderFactory(IProviderConfigurationService providerConfigurationService) 
     { 
      _providerConfigurationService = providerConfigurationService; 
     } 

     public Collection<IProvider> GetProviderInstances(SearchType searchType) 
     { 
      // Provider configuration service will read a XML/DB store to retrieve list of search providers applicable for a search type 
      var providerList = _providerConfigurationService.GetProviderList(searchType); 
      return new Collection<IProvider>(providerList.ForEach(x=> ServiceLocator.GetInstance(typeof(x))).ToList()) ; 
     } 
    } 

我有什麼其他選擇?我目前正在使用Unity for DI。

+0

爲什麼你首先需要這麼多的依賴對象? – Muctadir

+0

根據搜索類型的不同,我需要調用一組不同的提供者。這也是一般工廠模式的一個有效問題,因爲它的工作是創建違背IoC原則的具體實例。 –

+0

您使用的是什麼DI框架? Ninject有一個非常棒的工廠擴展。 –

回答

2

另一種方法是一個Func<Type, object>傳遞給構造函數,並通過你的容器來實現功能:

unity.RegisterInstance<Func<Type, object>>(t => unity.Resolve(t)) 

然後在你的類:

public ProviderFactory(Func<Type, object> createFunc, IProviderConfigurationService pcs) 
{ 
    _createFunc = createFunc; 
} 

public Collection<IProvider> GetProviderInstances(SearchType searchType) 
{ 
    var providerList = _providerConfigurationService.GetProviderList(searchType); 
    return new Collection<IProvider>(providerList.Select(_createFunc).ToList()); 
} 
+0

非常感謝@Bojan,但是看起來它只是掩蓋了通過DI容器周圍的真正屬於服務位置的灰色區域,尤其是因爲Func 對於它的意義並不清楚。 –

+0

我同意不清楚立即做什麼 - 但也很容易用更具描述性的名稱聲明委託(例如'public object delegate CreateProvider(Type providerType)')並使用它來代替'Func'。但是,正如通常所理解的那樣,這裏沒有服務地點。任何方法都必須在底層使用DI容器,我認爲這樣做不一定是壞事,但使用委託代替允許更方便的測試。 –

+0

我認爲代表的方式打大獎。它將類型的解析與其他代碼封裝在一起,並且委託是命名屬性,對於額外的點,我強烈地鍵入委託來返回IProvider,因此您無法輕易濫用它。 –

1

您缺少抽象。

您的ProviderFactory應該實現IProviderFactory抽象。這樣,您可以將該接口放置在應用程序的基本庫中,並且可以將ProviderFactory實現放入您的Composition Root中。對於位於組合根內部的代碼,可以引用DI庫,在這種情況下you're not using service location

+0

那麼你是否主張具體的ProviderFactory存在於Global.asax(這是一個WebAPI項目),並且直接引用了DI容器,或者我正在讀取錯誤的東西? ProviderFactory包含業務邏輯,我不確定這是否合適。 –

+0

是的,我主張所有引用容器的類都存在於組合根目錄中。我不主張的是,您將業務邏輯放在組合根目錄中。確保業務邏輯放置在您的應用程序中,並且不會引用容器。你應該相應地改變你的設計。 – Steven

+0

有沒有一個簡單的例子可以顯示?我無法在引導階段將您的解決方案與我的DI容器正常註冊IProviderFactory相關聯,或者通過構造器注入將DI容器傳遞給ProviderFactory。 –

0

我最近解決了一個非常類似的問題在我自己的代碼中使用DI框架。爲了滿足依賴倒置,工廠構造函數應該接受一個接口(正如其他答案所說的那樣),但是爲了讓框架注入正確的類型非常棘手,而沒有詳細列出每個可能的結論的大量參數列表。

SimpleInjector讓您與註冊一個給定的所有抽象結核:

Container.RegisterCollection(typeof(IProvider), new [] {typeof(TKnown).Assembly,...}); 

你的XML可以列出其中結核定義(可能是外部)的組件,你可以從那裏建立組裝陣列。那麼你的工廠只需要接受它們並選擇一個,也許基於你提到的searchType。

public class ProviderFactory 
{ 
    private List<IProvider> providers; 
    public ProviderFactory(IEnumerable<IProvider> providers) 
    { 
     this.providers = providers.ToList(); 
    } 

    public IProvider GetProvider(string searchType) 
    { 
     // using a switch here would open the factory to modification 
     // which would break OCP 
     var provider = providers.SingleOrDefault(concretion => concretion.GetType().Name == searchType); 

     if (provider == null) throw new Exception("No provider found of that type. Are you missing an assembly in the RegisterCollection for IProvider?"); 

     return provider; 
    } 

我知道我方式晚於這個黨,但假設其他人看不到這種做法是有問題的,它可能是有用的。