2

最近我讀了很多關於應用程序設計模式的東西:關於DI,SL反模式,AOP等等。其原因 - 我想達成設計的妥協:鬆散耦合,乾淨,易於使用。除了一個問題之外,DI似乎幾乎是一種解決方案:跨領域和可選的依賴導致構造或財產污染。所以我來爲我自己的解決方案和我想知道你怎麼看它依賴注入+環境上下文+服務定位器

Mark Seemann(DI書的作者,着名的「SL is anti-patter」聲明)在他的書中提到了一種叫做Ambient Context的模式。雖然他說他不太喜歡它,但這種模式仍然很有趣:它就像舊的單身人士,除了它是作用域並提供默認值的,所以我們不必檢查null。它有一個缺陷 - 它沒有,它不知道它的範圍和如何處理它自己。

那麼,爲什麼不在這裏應用服務定位器?它可以解決範圍和處理環境上下文對象的問題。在你說它是反模式之前:這是你隱藏合約的時候。但在我們的情況下,我們隱藏可選合約,所以它不是那麼糟糕IMO。

這裏是一些代碼來說明我的意思:

public interface ILogger 
{ 
    void Log(String text); 
} 

public interface ISomeRepository 
{ 
    // skipped 
} 


public class NullLogger : ILogger 
{ 
    #region ILogger Members 

    public void Log(string text) 
    { 
     // do nothing 
    } 

    #endregion 
} 

public class LoggerContext 
{ 
    public static ILogger Current 
    { 
     get 
     { 
      if(ServiceLocator.Current == null) 
      { 
       return new NullLogger(); 
      } 
      var instance = ServiceLocator.Current.GetInstance<ILogger>(); 
      if (instance == null) 
      { 
       instance = new NullLogger(); 
      } 
      return instance; 
     } 
    } 
} 

public class SomeService(ISomeRepository repository) 
{ 
    public void DoSomething() 
    { 
     LoggerContext.Current.Log("Log something"); 
    } 
} 

編輯:我知道,不問具體問題與堆棧溢出設計衝突去。因此,我將標記爲最好的描述爲什麼這個設計不好或更好的解決方案(或者可能是另外的?)的答案。但是,不建議AOP,這很好,但是當你真的想在你的代碼中做些什麼的時候,這不是一個解決方案。

編輯2:我添加了一個檢查ServiceLocator.Current爲空。這就是我想要我的代碼所做的:在未配置SL時使用默認設置。

+0

我從你的問題中遺漏了一個例子,你清楚地表明你需要使用'LoggerContext.Current'而不是在代碼中注入一個'ILogger'。根據我的經驗,如果您需要在代碼中注入許多'ILogger'依賴項,您要麼記錄太多(而不是拋出異常),或者您沒有遵守SRP(實際上您需要AOP)。看看這個答案:http://stackoverflow.com/questions/9892137/windsor-pulling-transient-objects-from-the-container。 – Steven

回答

3

作爲您所提議的環境環境的問題是,它使測試變得更加困難。出於以下原因:

  1. 運行單元測試時,必須始終在「ServiceLocator.Current」中註冊一個有效的實例。但不僅如此,它必須註冊有效的​​。
  2. 當你需要在一個測試中使用假記錄器(除了簡單的 NullLogger),你將不得不配置你的容器,因爲 沒有辦法掛鉤到這個,但由於容器是一個單身,所有其他測試將使用相同的記錄器。
  3. 這將是非平凡的(並浪費時間)來創建一個解決方案,當您的單元測試並行運行時(如MSTest默認)。

所有這些問題都可以通過簡單地將​​實例注入到需要它的服務中來解決,而不是使用環境上下文。

並且如果系統中的許多類取決於那個抽象的​​,那麼您應該認真詢問您的自我whether you're logging too much

另請注意,dependencies should hardly ever be optional

+0

1. Steven,我忘了檢查ServiceLocator.Current爲null,謝謝指出。通過這個檢查測試不會強制初始化SL。 2.爲什麼我應該首先測試像ILogger這樣的東西?但如果我想它仍然可行。 3.我同意 - 如果你想測試它,這是一個問題。但是,再次 - 這是可行的。最後,更重要的是:輕鬆寫出真實的代碼還是簡單地編寫測試? –

+1

3.有很多研究證明,如果不能測試它,就不能編寫高質量的代碼。所以能夠輕鬆編寫測試對於編寫高質量的應用程序至關重要。 –

+1

@DmitryGolubets:讓我轉過來。你爲什麼不想在一個類中測試一個ILogger的用法?既然你寫了這個,這必須是有價值的業務邏輯,你應該想知道這段代碼是否正確。如果這個代碼不是很有價值,爲什麼不把它寫在第一位呢?也許這條線是一個橫切關注點(AOP),並不是重要的業務邏輯。在這種情況下,你不應該使業務邏輯複雜化,你應該將這個邏輯提取到裝飾器或某種類型。 – Steven

3

您可以使用手工製作的裝飾師或某種截取手段(例如Castle DynamicProxyUnity's interception extension)添加橫切關注點。

因此,您不必將​​注入核心業務課程。

+0

是的,我到處都讀了同樣的建議,但對我來說並不合適。有時日誌應該寫在某個「if」分支中的方法內部。攔截並不能解決這個問題。 ILogger只是一個例子,但是可以有其他接口來讀取一些值。 –

+2

@DmitryGolubets:如果您認爲日誌記錄是您班級的一個重要方面,那就明確一點,並使用構造函數注入來注入它。如果它用於跟蹤輸入值和日誌記錄異常(我將日誌和跟蹤視爲根本不同的事情!)使用裝飾器或攔截器。但永遠不要隱藏依賴!那會比以後更快地咬你。總是。 –

+0

如果你想讓記錄器成爲封裝類邏輯的一部分,也許最好是把你的類拆分成幾個細粒度的類,然後分別裝飾它們? –