2010-06-09 65 views
29

我們想使用Unity for IOC。 我所看到的全部是有一個全局靜態服務(讓我們稱之爲IOCService)的實現,它持有對Unity容器的引用,該容器註冊所有接口/類組合,並且每個類都會詢問該對象:給我一個實施Ithis或IThat。如何在沒有全局靜態服務(非服務定位器解決方案)的情況下實現IOC?

通常我會看到這種模式不好的響應,因爲它導致從所有類到IOCService(而不是Unity容器,因爲它只在IOCService中知道)的依賴關係。

但我不經常看到的是:什麼是替代方式?

米歇爾

編輯:發現了全局靜態服務被稱爲服務定位器,補充說,爲標題。

+0

開始賞金,對不起點的低量,但我只有一個賺了:-) – Michel 2010-06-20 12:21:59

+1

如果您想更詳細的治療我正在寫一本關於它的書:http://www.manning.com/seemann/ – 2010-06-25 08:52:25

回答

11

另一種方法是有你的容器的最高應用水平只能爲單一實例。,然後使用該容器來解決,你需要在該層創建的每一個對象實例

例如,大多數可執行文件的主要方法看起來就像這樣(負異常處理):

private static void main(string[] args) { 

    Container container = new Container(); 

    // Configure the container - by hand or via file 

    IProgramLogic logic = container.Resolve<IProgramLogic>(); 

    logic.Run(); 
} 

您的程序(此處由IProgramLogic實例表示)不必知道有關您的容器的任何信息,因爲container.Resolve將創建它的所有依賴關係及其依賴關係的依賴關係,而不依賴於它們自己的依賴關係。


由於Web表單不支持構造函數注入,所以ASP.NET更加困難。我通常在我的Web表單應用程序中使用Model-View-Presenter,所以我的Page類在他們的演示者中實際上只有一個依賴關係。我不會單元測試它們(一切有趣和可測試的東西都在我的演示者中,我測試的是,我從來不會替換演示者。所以,我不打了框架 - 我只是公開一個容器屬性上我HttpApplication類(在的global.asax.cs),直接從我的Page文件使用它:

protected void Page_Load(object sender, EventArgs args) { 
    ICustomerPresenter presenter = Global.Container.Resolve<ICustomerPresenter>(); 
    presenter.Load(); 
} 

那是當然的服務定位器 - 雖然Page類是耦合到定位器的唯一東西:您的演示者及其所有依賴項仍與您的IoC容器實現完全分離。

如果你有很大的依賴性在Page文件(即,如果你不使用模型 - 視圖 - 演示),或者如果它是很重要的,你從你的Global應用程序類解耦Page類,你應該嘗試找到一個集成到Web表單請求管道並使用屬性注入的框架(如Nicholas在下面的評論中所建議的那樣) - 或者編寫自己的IHttpModule並自己執行屬性注入。

+0

我以非常類似的方式執行此操作。就我而言,Global是IoCContainer,Container是GetInstance。這是整個應用程序中唯一一個手動編寫單例的類(所有其他類都是單例或不依賴於Unity配置)。此類(Global/IoCContainer)位於名爲Configuration的項目中。該項目僅由UI引用,並且它引用所有項目(UI除外)以便能夠實例化適當的對象。 – bloparod 2010-06-09 14:34:43

+2

這仍然是經常建議的模式(服務定位器)。即使在ASP.NET WebForms中,也可以通過屬性注入(http://code.google.com/p/autofac/wiki/AspNetIntegration#Implementing_WebForms_Pages_and_User_Controls)或通過重構演示者驅動的方法來實現依賴注入。 – 2010-06-09 21:27:55

+0

我同意尼古拉斯;除了這個答案聽起來合乎邏輯,它仍然是一個服務定位器? – Michel 2010-06-10 08:23:38

5

不是明確使用容器,而是通過利用構造函數/屬性注入來隱式使用它。創建一個依賴於應用程序所有主要部分的核心類(或一組核心類)。

大多數容器會讓你把ISomething[]放在你的構造函數中,它會將ISomething的所有實例注入到你的類中。

這樣,當你引導你的應用程序:

  1. 實例化容器
  2. 註冊你所有的好東西
  3. 解決的核心類(這將拉在你需要的所有其他相關性)
  4. 運行應用程序的「主要」部分

現在,根據您的應用程序類型正在編寫,有不同的策略來避免將IoC容器標記爲「靜態」。

對於ASP.NET Web應用程序,您最終可能會將容器存儲在應用程序狀態中。對於ASP.NET MVC應用程序,您需要更換Controller Factory。

對於桌面應用程序,事情變得更加複雜。 Caliburn使用一個有趣的解決方案,以使用IResult結構(這是WPF應用程序,但可以適用於Windows窗體以及這個問題。

+0

Unity在構造函數解析中不會解析'ISomething []'或'IEnumerable '。如果您需要傳遞已註冊依賴項的列表,您必須使用'InjectionConstructor'。這種情況很糟糕,因爲這意味着這些依賴項在啓動時會解決一次,因此不允許瞬態生命週期。很高興知道是否有更好的解決方案。 – 2010-06-10 13:07:03

+0

如果您需要在運行時將新實例注入代碼,那麼您應該有明確的方式(不是由IoC容器注入)發生。您可以設置一個事件聚合器來通知特定類型的新實例何時到達。 – 2010-06-23 23:25:38

+0

@Igor - 你也可以通過注入一個可以在運行時創建新的瞬態實例的工廠來解決這個問題。 – 2010-06-24 13:54:40

2

如果您的問題在整個應用程序中對Unity具有依賴性,您可以將服務定位器與外觀結合以隱藏IOC實現。通過這種方式,您不需要在應用程序中創建Unity的依賴關係,只需要即可爲您解析類型的東西

例如:

public interface IContainer 
{ 
    void Register<TAbstraction,TImplementation>(); 
    void RegisterThis<T>(T instance); 
    T Get<T>(); 
} 

public static class Container 
{ 
    static readonly IContainer container; 

    public static InitializeWith(IContainer containerImplementation) 
    { 
     container = containerImplementation; 
    } 

    public static void Register<TAbstraction, TImplementation>() 
    { 
     container.Register<TAbstraction, TImplementation>(); 
    } 

    public static void RegisterThis<T>(T instance) 
    { 
     container.RegisterThis<T>(instance); 
    } 

    public static T Get<T>() 
    { 
     return container.Get<T>(); 
    } 
} 

現在,所有你需要的是一個​​實施您選擇的IOC容器。

public class UnityContainerImplementation : IContainer 
{ 
    IUnityContainer container; 

    public UnityContainerImplementation(IUnityContainer container) 
    { 
     this.container = container; 
    } 

    public void Register<TAbstraction, TImplementation>() 
    { 
     container.Register<TAbstraction, TImplementation>(); 
    } 

    public void RegisterThis<T>(T instance) 
    { 
     container.RegisterInstance<T>(instance); 
    } 

    public T Get<T>() 
    { 
     return container.Resolve<T>(); 
    } 
} 

現在您有一個服務定位器,它是IOC服務的外觀,並且可以配置您的服務定位器以使用Unity或任何其他IOC容器。應用程序的其餘部分不依賴於IOC實現。

要配置的服務定位:

IUnityContainer unityContainer = new UnityContainer(); 
UnityContainerImplementation containerImpl = new UnityContainerImplementation(unityContainer); 
Container.InitializeWith(containerImpl); 

爲了進行測試,您可以創建​​存根返回任何你想要的,並初始化Container這一點。

+5

服務定位器*是問題(至少對於很多人來說),而不僅僅是對特定依賴注入容器的具體依賴。 Mark Seemann [已經很清楚地寫了服務定位器的問題](http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx)。 – 2010-06-10 13:39:19

+0

確實不錯,但很多人對服務定位器模式有問題(因爲它創建了服務定位器類的依賴關係,而不僅僅是Unity類),但問題是我從來沒有看到一個好的替代方案,這就是爲什麼我問。 – Michel 2010-06-10 15:26:18

+0

@Jeff:你鏈接中的文章很好 – Michel 2010-06-10 15:32:31

4

理論上,不必擔心擁有靜態IoC實例,您需要遵循搏擊俱樂部規則 - 即不要談論戰鬥俱樂部 - 即不提及IoC容器。

這意味着您的組件應該很大程度上不了解IoC容器。它只能在註冊組件時在最高層使用。如果一個類需要解析某些東西,那麼它應該真的作爲一個依賴注入。

瑣碎的情況很簡單。如果PaymentService取決於IAccount,後者應該由IoC來注入:

interface IAccount { 
    Deposit(int amount); 
} 

interface CreditCardAccount : IAccount { 
    void Deposit(int amount) {/*implementation*/} 
    int CheckBalance() {/*implementation*/} 
} 

class PaymentService { 

    IAccount account; 

    public PaymentService (IAccount account) { 
    this.account = account; 
    } 

    public void ProcessPayment() { 
    account.Deposit(5); 
    } 
} 
//Registration looks something like this 
container.RegisterType<IAccount, CreditCardAccount>(); 
container.RegisterType<PaymentService>(); 

不那麼簡單的情況就是要注入多個註冊。特別適用於任何類型的Converntion Over Configuration並通過名稱創建對象。

有關付款例如,假設你想通過所有帳戶枚舉並檢查他們的餘額:

class PaymentService { 

    IEnumerable<IAccount> accounts; 

    public PaymentService (IEnumerable<IAccount> accounts) { 
    this.accounts = accounts; 
    } 

    public void ProcessPayment() { 
    foreach(var account in accounts) { 
     account.Chackbalance(); 
    } 
    } 
} 

團結有註冊多個接口類的映射(他們必須有不同的名字還以爲)的能力。但是,它不會自動將它們注入到那些註冊接口集合的類中。所以,上面的例子會在運行時拋出一個解決失敗的異常。

如果你不在乎這些對象永遠活着,你可以在一個更靜態的方式登記PaymentService

container.RegisterType<PaymentService>(new InjectionConstructor(container.ResolveAll<IAccount>())); 

上面的代碼將註冊PaymentService並會使用IAccount實例的集合,即解決在註冊時。

或者,您可以傳遞容器本身的一個實例作爲依賴關係,並讓PaymentService執行帳戶的解析。這並不完全符合撲克俱樂部規則,但比靜態服務定位器稍微有點臭。

class PaymentService { 

    IEnumerable<IAccount> accounts; 

    public PaymentService (IUnityContainer container) { 
    this.accounts = container.ResolveAll<IAccount>(); 
    } 

    public void ProcessPayment() { 
    foreach(var account in accounts) { 
     account.Chackbalance(); 
    } 
    } 
} 
//Registration is pretty clean in this case 
container.RegisterType<IAccount, CreditCardAccount>(); 
container.RegisterType<PaymentService>(); 
container.RegisterInstance<IUnityContainer>(container); 
7

+1 知道即服務定位是一件壞事

問題是 - Unity不是非常複雜,所以我不知道它是多麼容易/難以用它正確地使用IoC。

最近我寫了幾篇博文,你可能會覺得有用。

+0

+1這些文章總結得非常好。 Unity理解構造函數注入,所以第一篇文章直接翻譯。 AFAIK,Unity不支持動態發佈的工廠,但關於Abstract Factory的使用模式仍然適用 - 您只需手動實現具體的工廠,但這樣做非常微不足道。 – 2010-06-25 08:51:05

+0

Unity 2.0支持基於委託的自動工廠。 – 2010-08-24 00:19:24

相關問題