
From: http://huan-lin.blogspot.com/2011/11/dependency-injection-6.html
------------------------------------------------------
續上集,接著要談 Ambient Context 與 Service Locator 模式。
Ambient Context 模式
前述三種注入相依物件的方式,有些場合可能不適用,例如:應用程式特定執行環境的範圍內需要共享特定物件。碰到這種場合,便可以考慮採用 **Ambient Context **(環境脈絡)模式來解決。
Ambient Context又叫做 Context Object(環境物件),是一種常見的設計模式,主要用於跨階層、跨模組共享物件、界定程式執行區塊的範圍、以及提供橫切面的功能(cross- cutting concerns)。這些到處都需要的物件或服務,不太可能一一注入到每個需要它們的地方:一來過於繁瑣,二來有些子模組或程式區塊是碰觸不到、或不在控 制範圍內的。因此,Ambient Context 沒有明顯「注入物件」的味道;它不是侵入性的,而是在某個地方已經準備好、被動地等著別人來取用。此特性在某些場合正好可以彌補前述注入方式的不足,故在 此一併討論。
已知應用例
.NET 類別庫中提供交易管理功能的 System.Transactions.TransactionScope 就是 Ambient Context 的一個例子。以下程式片段示範了基礎用法。
using (TransactionScope trxScope = new TransactionScope())
{
// 執行多項資料異動作業。
order.Add(newOrder);
customer.LastOrderDate = DateTime.Now;
trxScope.Complete(); // 確認交易。
}
此外,ASP.NET 應用程式經常會用 Http.Web.HttpContext.Current 來取得目前的 HttpContext 物件。這也是一個常見的例子。
範例程式(一)
如前面提過的,Ambient Context 模式可用於程式特定執行範圍內共享物件狀態,此「特定範圍」可以是整個應用程式、特定執行緒、或其他自訂的執行範圍。如果是整個應用程式範圍內皆可存取的 共享物件,實作起來相當容易,通常用一個公開的靜態類別和靜態屬性就能達成。例如以下程式片段:
public static AppShared
{
private static ILogger _logger = new MyLogger();
public static ILogger Logger
{
get { return _logger; }
set { _logger = value; }
}
}
每當應用程式需要寫入日誌訊息時,在任何地方皆可使用如下方式達成:
AppShared.Logger.Info("請謹慎使用靜態變數和全域變數。");
範例程式(二)
這裡再提供一個範例,示範如何實作一個依個別執行緒(per thread)共享物件資訊的 Ambient Context 類別。此類別會使用 .NET Framework 4.0 之後提供的 ThreadLocal<T> 來保存個別執行緒的狀態資訊。
令此 Ambient Context 類別名稱為 PerThreadContext,而且它要提供一個靜態的 Current 屬性,供外界取得當前的 context 物件。如此一來,用戶端程式可以透過以下方式取得當前執行緒 context 中的共享物件:
var obj = PerThreadContext.Current.SomeMember;
PerThreadContext 類別的程式碼如下:
public class PerThreadContext
{
// 用一個靜態的 ThreadLocal<T> 來管理各執行緒的 context 物件。
private static ThreadLocal<PerThreadContext> _threadedContext;
static PerThreadContext()
{
_threadedContext = new ThreadLocal<PerThreadContext>();
}
// 共享的狀態
public DateTime OnceUponATime { get; set; }
// 把建構函式宣告為私有,不讓外界任意 context 物件。
private PerThreadContext()
{
OnceUponATime = DateTime.Now;
}
public static PerThreadContext Current
{
get
{
// 如果目前的執行緒中沒有 context 物件...
if (_threadedContext.IsValueCreated == false)
{
// 就建立一個,並保存至 thread-local storage。
_threadedContext.Value = new PerThreadContext();
}
return _threadedContext.Value;
}
}
}
這裡使用了延遲初始化(lazy initialization)的技巧:當用戶端程式透過靜態屬性 Current 取得當下的 context 物件時,先檢查目前的執行緒中有沒有 context 物件,有則直接傳回物件參考,若沒有,便建立一個,並保存至目前執行緒專屬的儲存區(thread-local storage)。其中的公開物件屬性 OnceUponATime 代表要與其他物件共享的狀態。
我們可以用一個簡單的 Console 程式來觀察其運作機制:
static void Main(string[] args)
{
ShowTime();
System.Threading.Thread.Sleep(2000);
var t1 = new Thread(ShowTime);
var t2 = new Thread(ShowTime);
t1.Start();
System.Threading.Thread.Sleep(2000);
t2.Start();
System.Threading.Thread.Sleep(2000);
ShowTime();
/* 執行結果:
Thread 1: 2014/5/4 下午 01:37:09
Thread 3: 2014/5/4 下午 01:37:11
Thread 4: 2014/5/4 下午 01:37:13
Thread 1: 2014/5/4 下午 01:37:09
*/
}
static void ShowTime()
{
Console.WriteLine("Thread {0}: {1} ",
Thread.CurrentThread.ManagedThreadId,
PerThreadContext.Current.OnceUponATime);
}
執行結果顯示,同樣是印出 PerThreadContext.Current.OnceUponATime 屬性值,不同的執行緒會有不同的結果。
Service Locator 模式
Service Locator(服務定位器)是一種設計模式,它同時具有前面提過的 Ambient Context 和 Factory 模式的性質,而且經常與 DI 搭配使用(儘管頗具爭議),故在此一併介紹。
顧名思義,Service Locator 的功能是用來尋找應用程式所需的服務,並返回該服務的執行個體。說得更具體些,當用戶端需要特定介面(或抽象類別)的物件時,既不使用 new 來建立物件,也不使用注入物件的機制,而是向 Service Locator 要一個物件。其基本運作機制如下:
用戶端向 Service Locator 提出請求,要求一個符合 IServiceA 的物件。
Service Locator 透過本身的型別搜尋/對應機制來尋找符合(相容於) IServiceA 介面的具象類別,然後建立該類別的物件實體,並回傳給用戶端。
下圖為 Service Locator 模式的結構圖。