程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 輕松編程: 通過理順軟件的依賴關系提高應用程序靈活性

輕松編程: 通過理順軟件的依賴關系提高應用程序靈活性

編輯:關於.NET

本文討論:

緊密耦合體系結構的錯誤之處

測試和依賴關系災難

依賴關系反轉

依賴關系注入

本文使用了以下技術:

.NET Framework

幾乎所有人都認為追求松 散耦合設計不是明智之舉。遺憾地是,我們所設計軟件的緊密耦合程度通常都遠遠超過我們的預期。如何 了解設計是否耦合緊密?可使用靜態分析工具(如 Ndepend)來分析依賴關系,但了解應用程序中耦合程 度最輕松的辦法是嘗試獨立地實例化一個類。

從業務層中選取一個類(如 InvoiceService),然後將其代碼復制到一個新的控制台項目中。嘗試對 它進行編譯。您很可能會丟失某些依賴關系(如 Invoice、InvoiceValidator 等等)。將這些類復制到 此控制台項目並重試。您又會發現缺少其他類。最終可執行編譯時,您可能會發現新項目中已存在絕大部 分基本代碼。就像拉動毛線衫的一個松動線頭就可以拆掉整件衣服一樣。在您的設計中,每個類都直接或 間接地與其他類耦合。更改此類系統的確非常困難,因為任何一個類中的更改都可能牽連到系統的其余部 分。

解決這個問題的要點不是完全避開耦合,因為那是不可能的。例如:

string name 

= "James Kovacs";

我已將我的代碼耦合到 Microsoft® .NET Framework 中的 System.String 類。這不妥帖嗎?我不這樣認為。System.String 類意外發生更改的可能性非常低,也少 有可能因要求改變而必須修改我與 System.String 交互的方式。因此我對該耦合相當放心。此示例的用 意在於指出並非一味地消除耦合,而是要了解它並確保明智地選用耦合。

以經常出現在許多應用 程序數據層中的另一代碼為例:

SqlConnection conn = new SqlConnection

(connectionString);

或者以下代碼:

XmlDocument settings = new XmlDocument();
settings.Load("Settings.xml");

對於數據層將僅與 SQL Server® 交互或者始 終從名為 Settings.xml 的 XML 文檔中加載應用程序設置而言,您有多大把握?我們的目的不是構建一 個可無限擴展但極其復雜、無法使用的泛型框架。而是與可逆性相關。您能輕松地改變設計決策嗎?您的 應用程序體系結構能否很好地響應變化?

為何我如此關注變化?因為就是這個行業中唯一不變的 就是變化。需求、技術、開發人員以及業務都在變化。您有無做好應變准備?通過創建松散耦合設計,軟 件就可更好地響應許多時候無法預計的必然變化。

內部依賴關系問題

讓我們來研究一下常 見分層式應用程序體系結構中出現的一個典型高度耦合設計(請參見圖 1)。一個簡單的分層架構具有一 個 UI 層,它與服務(或業務)層交互,而服務(或業務)層又與存儲庫(或數據)層交互。這些層之間 的依賴關系也以相同順序向下傳遞。存儲庫層不了解服務層,而服務層又不了解 UI 層。

Figure 1 典型的分層式體系結構

有時您會擁有更多層(如表示層或工作流層),但每層僅了解其下一層的模式是一致的。將層作為連 貫的責任群集是個不錯的設計技術。然而,將上層和下層直接耦合會增加耦合並致使應用程序難以測試。

為什麼我如此關注可測試性?因為可測試性是衡量耦合的一項良好標准。如果無法在測試中輕松 地實例化某個類,則可能是耦合有些問題。例如,服務層非常熟悉並依賴存儲庫層。無法脫離存儲庫層測 試服務層。在實際應用中,即意味著大多數測試都會訪問基礎數據庫、文件系統或網絡。這會導致大量問 題,包括測試速度緩慢以及維護成本偏高。

測試速度緩慢 :如果測試可嚴格控制在內存中執行, 則每個測試的時間只有幾毫秒。如果測試訪問外部資源(如數據庫、文件系統或網絡),則每個測試的時 間通常會為 100 毫秒甚至更長時間。對於測試覆蓋率良好的典型項目來說,它有成百上千個測試,這意 味著是在幾秒內完成測試還是在幾分鐘或幾小時完成。

出錯隔離較差 :數據層組件的故障常常導致也無法測試上層組件。不是只有幾個測試失敗(這種情況 可快速隔離問題),而是有成百上千個測試失敗,從而使得難以找出問題且更加耗時。

維護成本 偏高 :大多數測試都需要一些初始數據。如果這些測試涉及數據庫,則必須在每次測試前確保數據庫處 於已知狀態。此外,必須確保每個測試的初始數據均獨立於其他測試的初始數據,否則可能遇到測試排序 問題,此時會由於無序而致使某些測試失敗。使數據庫保持正常已知狀態非常耗時且容易出錯。

此外,如果需要更改下層的實現,則往往不得不同時修改上層,因為這些層對下層具有隱式或顯式依賴關 系。盡管應用程序已分層,卻仍未得到松散耦合。

讓我們來看個具體示例 — 一個接受發票 的服務(請參見圖 2)。要使 InvoiceService.Submit 能接受發票提交,需依賴在類的構造函數中創建 的 AuthorizationService、InvoiceValidator 和 InvoiceRepository。無法在缺少具體依賴關系的情況 下對 InvoiceService 執行單元測試。這表示在運行單元測試前,必須確保:當 InvoiceRepository 插 入新發票時,不會在數據庫中產生任何主鍵或唯一鍵沖突,InvoiceValidator 也不得報告任何驗證失敗 情況。還必須確保運行單元測試的用戶具有適當的權限,這樣 AuthorizationService 才會允許“ 提交”操作。

Figure 2 Invoice Service

public class InvoiceService {
 private readonly AuthorizationService authoriazationService;
 private readonly InvoiceValidator invoiceValidator;
 private readonly InvoiceRepository invoiceRepository;
 public InvoiceService() {
  authoriazationService = new AuthorizationService();
  invoiceValidator = new InvoiceValidator();
  invoiceRepository = new InvoiceRepository();
 }
 public ValidationResults Submit(Invoice invoice) {
  ValidationResults results;
  CheckPermissions(invoice, InvoiceAction.Submit);
  results = ValidateInvoice(invoice);
  SaveInvoice(invoice);
  return results;
 }
 private void CheckPermissions(Invoice invoice, InvoiceAction action) {
  if(authoriazationService.IsActionAllowed(invoice, action) == false) {
   throw new SecurityException(
    "Insufficient permissions to submit this invoice");
  }
 }
 private ValidationResults ValidateInvoice(Invoice invoice) {
  return invoiceValidator.Validate(invoice);
 }
 private void SaveInvoice(Invoice invoice) {
  invoiceRepository.Save(invoice);
 }
}

這一點很難實現。如果任何此類依賴組件存在問題(代碼或數據錯誤),InvoiceService 測 試都會突然失敗。即使測試通過,在數據庫中設置正確數據、執行測試以及清理測試所創建的數據用掉的 總執行時間將為數百毫秒。即使通過將測試分批並在批前後運行腳本來分攤掉設置和清理成本,較之在內 存中運行測試而言,執行時間仍要長得多。

此外,還有個更加微妙的問題。假設您希望為 InvoiceRepository 添加審核支持,將不得不創建 AuditingInvoiceRepository 或修改 InvoiceRepository 本身。由於 InvoiceService 及其子組件之間的耦合,您在向系統引入新功能方面的 選擇並不多。

依賴關系反轉

可從下層依賴關系分離上層組件 InvoiceService,方法是通 過接口而非具體類來與依賴關系交互:

public class InvoiceService : IInvoiceService {
  private readonly IAuthorizationService authService;
  private readonly IInvoiceValidator invoiceValidator;
  private readonly IInvoiceRepository invoiceRepository;
  ...
}

改為使用接口(或抽象基類)意味著可替換任何依賴關系的其他實現。除創建 InvoiceRepository 外,還可創建 AuditingInvoiceRepository(假設 AuditingInvoiceRepository 實 現 IInvoiceRepository)。它還意味著可在測試期間替換虛設或模擬。此設計技術稱為契約式程序設計 。

在上層和下層組件分離中應用的原理稱為依賴關系反轉原理。正如 Robert C. Martin 在文章 (objectmentor.com/resources/articles/dip.pdf) 中所說:“上層模塊不應依賴下層模塊。它們 都應依賴抽象。”

在本例中,InvoiceService 和 InvoiceRepository 現在都依賴 IinvoiceRepository 提供的抽象。然而,我並未完全解決問題,我只是轉移了問題。盡管具體實現僅依 賴接口,問題卻仍是具體類如何“發現”彼此的存在。

InvoiceService 仍需要其依賴關系的具體實現。可簡單地在 InvoiceService 的構造函數中實例化這 些依賴關系,但這與以前相比差別不大。如果希望使用 AuditingInvoiceRepository,則還必須修改 InvoiceService 以實例化 AuditingInvoiceRepository。此外,還需修改依賴 IInvoiceRepository 的 所有類以改為實例化 AuditingInvoiceRepository。並無任何簡便方法可將 InvoiceRepository 全局換 為 AuditingInvoiceRepository。

一個解決方案是使用工廠來創建 IInvoiceRepository 實例。 這樣就可通過更改工廠方法來集中轉換成 AuditingInvoiceRepository。此技術的另一個名稱是服務位置 ,負責管理實例的工廠類則稱為服務定位器:

public InvoiceService() {
 this.authorizationService =
  ServiceLocator.Find<IAuthorizationService>();
 this.invoiceValidator = ServiceLocator.Find<IInvoiceValidator>();
 this.invoiceRepository = ServiceLocator.Find<IInvoiceRepository>();
}

ServiceLocator 中的功能可基於自配置文件或數據庫讀取的數據,也可直接與代碼相關。在 任一情況下,均可集中創建依賴關系對象。

通過使用虛設或模擬對象而非實際實現來配置服務定 位器,可對單獨的組件執行單元測試。因此,例如,在測試過程中, ServiceLocator.Find<IInvoiceRepository> 可返回一個 FakeInvoiceRepository,它會在保存發 票時向其指定一個已知主鍵,但不會將發票實際保存到數據庫中。從而不必執行復雜的數據庫設置和清除 ,並會從虛設依賴關系返回已知數據(請參見側欄“虛設依賴關系是否明智?”)。

但是,服務位置有幾項缺點。首先,在更高層類中看不出依賴關系。僅通過查看代碼,根本無法從其公共 簽名中看出 InvoiceService 是依賴 AuthorizationService、InvoiceValidator 還是 InvoiceRepository。

如果需要為同一接口提供不同的具體類型,則必須求助於重載 Find 方法。 它要求您在實現工廠類時決定是否需要某個替代類型。例如,無法在部署特定 IInvoiceRepository 請求 時重新配置 ServiceLocator 以替代 AuditingInvoiceRepository。但是,即使具有這些缺點,服務位置 卻易於理解且勝過對依賴關系進行硬編碼。

依賴關系注入

虛設依賴關系是否明智?

您可能很想知道 “虛設”依賴關系是否危險。會不會得出錯誤的結果?實際情況是您 應該會有測試來確認依賴關系(如實際的 InvoiceRepository)是否正常運行。這些測試會與實際數據庫 交互並確認 InvoiceRepository 運行正常。

如果您知道 InvoiceRepository.Save 已在發揮作用 ,為什麼還需要使用依賴 InvoiceRepository 的每個測試來重新進行測試?結果只會是由於連接數據庫 而降低了上層測試的速度,並且如果 InvoiceRepository 存在問題,則不僅 InvoiceRepository 測試會 失敗,InvoiceService 測試和任何其他依賴 InvoiceRepository 的組件也會失敗。

如果 InvoiceService 測試失敗而 InvoiceRepository 未失敗,則表示缺少一個針對 InvoiceRepository 的 測試。集成測試(它使用具體依賴關系來測試組件)更擅長發現此類缺點。較之於使用虛設/模擬依賴關 系的單元測試,此類測試運行速度更慢但運行次數少。

現在,假設由於通過單元測試 InvoiceRepository 可以正常工作,您擁有兩個選擇。可創建和維護復雜腳本來確保數據庫中的數據正確 ,這樣 InvoiceRepository 可針對每個 InvoiceService 測試返回預期的數據。或者,可創建一個虛設 或模擬 InvoiceRepository 實現來返回預期數據。後者更容易實現,並且更實用。

在對上層組件 執行單元測試時,您希望提供其依賴關系的虛設或模擬實現。但是,與其使用虛設或模擬來配置服務定位 器,然後再讓上層組件查找它們,不如通過參數化構造函數將依賴關系直接傳遞給上層組件。此技術稱為 依賴關系注入。圖 3 顯示了一個示例。

Figure 3 Dependency Injection

[Test]
public void CanSubmitNewInvoice() {
 Invoice invoice = new Invoice();
 ValidationResults validationResults = new ValidationResults();
 IAuthorizationService authorizationService =
  mockery.CreateMock<IAuthorizationService>();
 IInvoiceValidator invoiceValidator =
  mockery.CreateMock<IInvoiceValidator>();
 IInvoiceRepository invoiceRepository =
  mockery.CreateMock<IInvoiceRepository>();
 using(mockery.Record()) {
  Expect.Call(authorizationService.IsActionAllowed(
   invoice, InvoiceAction.Submit)).Return(true);
  Expect.Call(invoiceValidator.Validate(invoice))
   .Return(validationResults);
  invoiceRepository.Save(invoice);
 }
 using(mockery.Playback()) {
  IInvoiceService service = new InvoiceService(authorizationService,
   invoiceValidator, invoiceRepository);
  service.Submit(invoice);
 }
}

在此示例中,我將創建 InvoiceService 依賴關系的虛設對象,然後將它們傳遞給 InvoiceService 構造函數。(有關虛設對象框架的詳細信息,請參見 Mark Seemann 的“單元測試 :探索 Test Double 的狀態集”,網址為 msdn.microsoft.com/msdnmag/issues/07/09/MockTesting。)總之,通過定義 InvoiceService 與虛設 的交互方式而非在運行測試後確認 InvoiceService 的狀態來指定它的行為。

通過使用依賴關系注入,可在單元測試中輕松地向上層組件提供其依賴關系。但是,在運行應用程序 或執行集成測試時,如何在單元測試以外的環境中找到類的依賴關系仍是個問題。期望 UI 層向服務層提 供其依賴關系或者服務層向存儲庫層提供其依賴關系並不明智。最終的問題會比開始時還糟糕。但是,讓 我們假設由 UI 層負責向服務層提供其依賴關系:

// Somewhere in UI Layer
InvoiceSubmissionPresenter presenter =
 new InvoiceSubmissionPresenter(
  new InvoiceService(
   new AuthorizationService(),
   new InvoiceValidator(),
   new InvoiceRepository()));

如您所看到的,UI 將不得不了解自身的依賴關系,還有依 賴關系的依賴關系,這樣無限下去直至數據層。這顯然不是個理想的解決方案。走出這一困境最輕松的辦 法就是使用我們稱為“廉價的依賴關系注入”的技術。

“廉價的依賴關系注入 ”使用上層組件的默認構造函數來提供依賴關系:

public InvoiceService() :
 this(new AuthorizationService(),
  new InvoiceValidator(),
  new InvoiceRepository()) { }

請注意我是如何向最重載構造函數進行委托的。它確保了 無論使用哪個構造函數來創建實例,類的初始化邏輯都相同。將類與具體依賴關系耦合的唯一位置是默認 構造函數。由於您仍具備重載構造函數,它可在單元測試期間提供類的依賴關系,所以仍能對類進行測試 。

容器

現在來介紹一下控制反轉 (IoC) 容器,在那裡集中管理依賴關系。實際上,與實 現類型相比,容器就是一部特殊的接口字典。IoC 容器最簡單的形式就是使用另一名稱的服務定位器。稍 後,我將研究容器比服務位置功能更多的基理。

回到目前的問題上來,您希望將 InvoiceService 從其依賴關系的具體實現中完全分離出來。就象軟件中的所有問題一樣,可通過添加另一中間層來解決這 一問題。引入依賴關系解決程序這一概念,它會將接口映射到某個具體實現。然後使用一個泛型方法來獲 取接口 T 並返回實現該接口的類型:

public interface IDependencyResolver {
  T Resolve<T>();
}

現在我們來實現 SimpleDependencyResolver,它使用字典來存儲接口與實現這些接口的對象 之間的映射信息。我們需要一個方法在一開始填充字典,這裡使用的是 Register<T>(object obj) 方法(請參見圖 4)。請注意:Register 方法無需位於 IDependencyResolver 接口上,因為只有 SimpleDependencyResolver 的創建者會注冊依賴關系。通常這是在應用程序啟動期間由 Main 方法中調 用的幫助程序類來完成。

Figure 4 SimpleDependencyResolver

public class SimpleDependencyResolver : IDependencyResolver
{
 private readonly Dictionary<Type, object> m_Types =
  new Dictionary<Type, object>();
  
 public T Resolve<T>() {
  return (T)m_Types[typeof(T)];
 }
 public void Register<T>(object obj) {
  if(obj is T == false) {
   throw new InvalidOperationException(
    string.Format("The supplied instance does not implement {0}",
    typeof(T).FullName));
  }
  m_Types.Add(typeof(T), obj);      
 }
}

CompanyService 如何查找 SimpleDependencyResolver 以便找到其依賴關系?可把 IDependencyResolver 傳到需要它的所有類中,但這一做法很快會變得非常繁瑣。最簡單的解決方案是使 用靜態網關模式將配置好的 SimpleDependencyResolver 實例放入一個可全局訪問的位置。(還可使用單 例模式,但眾所周知,單例很難測試。它們是緊密耦合代碼難以測試的主要原因之一,因為它們與全局變 量沒什麼差別。如果可能,盡量避免使用它們。)

接下來了解一下靜態網關,我將其稱為 IoC。 (另一可能名稱是 DependencyResolver,但 IoC 更簡明扼要。)IoC 上的靜態方法匹配 IdependencyResolver 上的方法。(請注意:IoC 不會實現 IdependencyResolver,因為靜態類無法實現 接口。)還有一個接受實際 IDependencyResolver 的 Initialize 方法。IoC 靜態網關僅將所有 Resolve<T> 請求轉發給配置好的 IdependencyResolver:

public class IoC {
private static IDependencyResolver s_Inner;
public static void Initialize(IDependencyResolver resolver) {
s_Inner = resolver;
}
public static T Resolve<T>() {
return s_Inner.Resolve<T>();
}
}

在應用程序啟動期間,使用配置好的 SimpleDependencyResolver 來初始化 IoC。現在,可在默認構 造函數中將“廉價的依賴關系注入”替換成 IoC.Resolve:

public InvoiceService() :
this(IoC.Resolve<IAuthorizationService>(),
IoC.Resolve<IInvoiceValidator>(),
IoC.Resolve<IInvoiceRepository>()) { }

請注意,在啟動程序啟動後,無需同步對 IdependencyResolver 的內部訪問(因為它僅會被讀取但永 遠不會更新)。

IoC 類還有另一好處 — 用作應用程序的防損壞層。如果要使用另一 IoC 容器,只需部署一個能實現 IdependencyResolver 的適配器即可。即使在整個應用程序中大量使用 IoC,也不會與任何特定容器產生 耦合。

成熟的 IoC 容器

可使用簡單的 IoC 容器(如 SimpleDependencyResolver)來集成各松散耦合的組件。但是,它缺少 成熟 IoC 容器所具有的許多功能,包括:

廣泛的配置選項(如 XML、代碼或腳本)

生存期管理(如單例、瞬態、單個線程或池線程)

自動綁定依賴關系

綁定新功能

接下來更加深入地討論每個功能。我將使用一個廣泛應用的開源 IoC 容器 Castle Windsor 作為具體 示例。許多容器都可通過外部 XML 文件進行配置。例如,可如下配置 Windsor:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<components>
<component id="Foo"
service="JamesKovacs.IoCArticle.IFoo, JamesKovacs.IoCArticle"
type="JamesKovacs.IoCArticle.Foo, JamesKovacs.IoCArticle"/>
</components>
</configuration>

由於修改後無需重新編譯應用程序,因此 XML 配置的優勢十分明顯 — 盡管常常需要重新啟動應用程 序更改才能生效。但它也並非沒有缺點:XML 配置很容易變得非常冗長、直到運行時才檢測到錯誤以及使 用 CLR 的反引號表示法(而非更常見的 C# 泛型表示法)來聲明泛型類型。

(Company.Application.IValidatorOf<Invoice> 編寫成 Company.Application.IValidatorOf`1[[Company.Application.Invoice, Company.Application]], Company.Application。)

除 XML 外,還可使用 C# 或其他與 Microsoft .NET Framework 兼容的語言來配置 Windsor。如果將 配置代碼隔離到一個單獨的程序集中,更改配置即意味著僅重新編譯配置程序集並重新啟動應用程序。

可使用 Binsor 這一專用於配置 Windsor 的域特定語言 (DSL) 來編寫 Windsor 配置的腳本。Binsor 允許使用 Boo 來編寫配置文件。(Boo 是一種注重語言和編譯器擴展性的靜態類型 CLR 語言,因而非常 適合於編寫 DSL。)在 Binsor 中,之前的 XML 配置文件可重新編寫為:

import JamesKovacs.IoCArticle
Component("Foo", IFoo, Foo)

當您發現 Boo 是一種成熟的編程語言後就更加有趣了,即意味著無需手動添加組件注冊即可使用 Binsor 在 Windsor 中自動注冊類型,就像使用基於 XML 的配置一樣:

import System.Reflection
serviceAssembly = Assembly.Load("JamesKovacs.IoCArticle.IoCContainer")
for type in serviceAssembly.GetTypes():
continue if type.IsInterface or type.IsAbstract or
type.GetInterfaces().Length == 0
Component(type.FullName, type.GetInterfaces()[0], type)

即使並不熟悉 Boo,代碼的用意仍是一清二楚。只需將新服務添加到 JamesKovacs.IoCArticle.Services 命名空間,該服務就會自動注冊為其服務接口的默認實現。假設創建 以下類:

public class AuthorizationService : IAuthorizationService {
...
}

如果任何其他類通過將 IAuthorizationService 作為其構造函數的參數來聲明與它的依賴關系, Binsor 將自動綁定,而無需在配置文件中明確指定該依賴關系!可在 ayende.com/Blog/category/451.aspx 上找到有關 Binsor 的更多信息,在 boo.codehaus.org 上找到有 關 Boo 的更多信息。

生存期管理

SimpleDependencyResolver 始終返回為接口注冊的同一實例,從而有效使得該實例成為單例。可修改 SimpleDependencyResolver 來注冊具體類型而非實例。然後,可使用各種工廠來創建具體類型的實例。 單例工廠將始終返回同一實例。瞬態工廠將始終返回一個新實例。單個線程工廠則針對每個請求線程維護 一個實例。

可實現您想得到的所有實例化策略。這就是 Windsor 所提供的功能。通過在 XML 配置文件中應用屬 性,可選擇將哪種類型的工廠用於創建特定具體類型的實例。默認情況下,Windsor 使用單例實例。如果 希望每次從容器請求 IFoo 時均返回一個新的 Foo,只需將配置更改為:

<component id="Foo"
service="JamesKovacs.IoCArticle.IFoo, JamesKovacs.IoCArticle"
type="JamesKovacs.IoCArticle.Foo, JamesKovacs.IoCArticle"
lifestyle="transient"/>

自動綁定依賴關系

自動綁定依賴關系表示容器可檢查請求類型的依賴關系並創建這些依賴關系,而無需開發人員提供默 認構造函數。

public InvoiceService(IAuthorizationService authorizationService,
IInvoiceValidator invoiceValidator,
IInvoiceRepository invoiceRepository) {
...
}

當客戶端向容器請求 IInvoiceService 時,容器會注意到具體類型需要 IauthorizationService、 IInvoiceValidator 和 IinvoiceRepository 的具體實現。它會查找適當的具體類型,解析它們可能擁有 的所有依賴關系並構造這些關系。然後使用這些依賴關系來創建 InvoiceService。自動綁定免去了維護 默認構造函數這一要求,因而簡化了代碼並刪除了許多類對 IoC 靜態網關的依賴關系。

通過編碼約定而非具體實現並使用容器,您的體系結構將更加靈活且易於更改。如何為 InvoiceRepository 實現可配置審核日志記錄?在緊密耦合體系結構中,必須修改 InvoiceRepository。 還需要一些應用程序配置設置來指明是否打開審核日志記錄。

在松散耦合體系結構中,有沒有更好的方法呢?您可實現 AuditingInvoiceRepositoryAuditor,它會 實現 IinvoiceRepository。審核程序僅實現審核功能,然後委托給其構造函數中提供的實際 InvoiceRepository。此模式稱為裝飾程序(請參見圖 5)。

Figure 5 Using the Decorator Pattern

public class AuditingInvoiceRepository : IInvoiceRepository {
private readonly IInvoiceRepository invoiceRepository;
private readonly IAuditWriter auditWriter;
public AuditingInvoiceRepository(IInvoiceRepository invoiceRepository,
IAuditWriter auditWriter) {
this.invoiceRepository = invoiceRepository;
this.auditWriter = auditWriter;
}
public void Save(Invoice invoice) {
auditWriter.WriteEntry("Invoice was written by a user.");
invoiceRepository.Save(invoice);
}
}

要打開審核,可將容器配置為在請求 IInvoiceRepository 時,返回 AuditingInvoiceRepository 所 裝飾的 InvoiceRepository。客戶端並不知曉其中內情,因為它們仍與 IinvoiceRepository 交互。這種 方法具有許多好處:

1.由於並未修改 InvoiceRepository,因此不可能會破壞其代碼。

2.可獨立於 InvoiceRepository 實現和測試 AuditingInvoiceRepository。因此無論有沒有實際數據 庫均可確保審核功能可正常運行。

3.可出於審核、安全性、緩存或其他目的構建多個裝飾程序,這不會增加 InvoiceRepository 的復雜 性。換句話說,在添加新功能時,松散耦合系統中的裝飾程序方法擴展性更好。

4.容器提供了有趣的應用程序可擴展性機制。不必在與 InvoiceRepository 或 IInvoiceRepository 相同的程序集中實現 AuditingInvoiceRepository。可在配置文件引用的第三方程序集中輕松地實現它。

輕松實現更改

即使軟件體系結構已劃分層次,各層之間仍可能緊密耦合,從而妨礙應用程序的測試和發展演變。但 是,您可取消設計的耦合。通過使用依賴關系反轉和依賴關系注入,即可從編碼約定(而非具體實現)中 受益。通過引入控制反轉容器,可增加體系結構的靈活性。最終,您的松散耦合設計將更加適應變化。

代碼下載位置: DependencyInjection2008_03.exe

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved