Attribute在攔截機制上的應用
從這一節開始我們討論Attribute的高級應用,為此我准備了一個實際的例子:我們有一個訂單處理系統,當一份訂單提交的時候,系統檢查庫存,如果庫存存量滿足訂單的數量,系統記錄訂單處理記錄,然後更新庫存,如果庫存存量低於訂單的數量,系統做相應的記錄,同時向庫存管理員發送郵件。為了方便演示,我們對例子進行了簡化:
//Inventory.cs using System; using System.Collections; namespace NiwalkerDemo { public class Inventory { private Hashtable inventory=new Hashtable(); public Inventory() { inventory["Item1"]=100; inventory["Item2"]=200; } public bool Checkout(string product, int quantity) { int qty=GetQuantity(product); return qty>=quantity; } public int GetQuantity(string product) { int qty=0; if(inventory[product]!=null) qty = (int)inventory[product]; return qty; } public void Update(string product, int quantity) { int qty=GetQuantity(product); inventory[product]=qty-quantity; } } } //Logbook.cs using System; namespace NiwalkerDemo { public class Logbook { public static void Log(string logData) { Console.WriteLine("log:{0}",logData); } } } //Order.cs using System; namespace NiwalkerDemo { public class Order { private int orderId; private string product; private int quantity; public Order(int orderId) { this.orderId=orderId; } public void Submit() { Inventory inventory=new Inventory(); //創建庫存對象 //檢查庫存 if(inventory.Checkout(product,quantity)) { Logbook.Log("Order"+orderId+" available"); inventory.Update(product,quantity); } else { Logbook.Log("Order"+orderId+" unavailable"); SendEmail(); } } public string ProductName { get{ return product; } set{ product=value; } } public int OrderId { get{ return orderId; } } public int Quantity { get{ return quantity;} set{ quantity=value; } } public void SendEmail() { Console.WriteLine("Send email to manager"); } } }
下面是調用程序:
//AppMain.cs using System; namespace NiwalkerDemo { public class AppMain { static void Main() { Order order1=new Order(100); order1.ProductName="Item1"; order1.Quantity=150; order1.Submit(); Order order2=new Order(101); order2.ProductName="Item2"; order2.Quantity=150; order2.Submit(); } } }
程序看上去還不錯,商務對象封裝了商務規則,運行的結果也符合要求。但是我好像聽到你在抱怨了,沒有嗎?當你的客戶的需求改變的時候(客戶總是經常改變他們的需求),比如庫存檢查的規則不是單一的檢查產品的數量,還要檢查產品是否被預訂的多種情況,那麼你需要改變Inventory的代碼,同時還要修改Order中的代碼,我們的例子只是一個簡單的商務邏輯,實際的情況比這個要復雜的多。問題在於Order對象同其他的對象之間是緊耦合的,從OOP的觀點出發,這樣的設計是有問題的,如果你寫出這樣的程序,至少不會在我的團隊裡面被Pass.
你說了:“No problem! 我們可以把商務邏輯抽出來放到一個專門設計的用來處理事務的對象中。”嗯,好主意,如果你是這麼想的,或許我還可以給你一個提議,使用Observer Design Pattern(觀察者設計模式):你可以使用delegate,在Order對象中定義一個BeforeSubmit和AfterSubmit事件,然後創建一個對象鏈表,將相關的對象插入到這個鏈表中,這樣就可以實現對Order提交事件的攔截,在Order提交之前和提交之後自動進行必要的事務處理。如果你感興趣的話,你可以自己動手來編寫這樣的一個代碼,或許還要考慮在分布式環境中(Order和Inventory不在一個地方)如何處理對象之間的交互問題。
幸運的是,.NET Framework中提供了實現這種技術的支持。在.NET Framework中的對象Remoting和組件服務中,有一個重要的攔截機制,在對象Remoting中,不同的應用程序之間的對象的交互需要穿越他們的域邊界,每一個應用域也可以細分為多個Context(上下文環境),每一個應用域也至少有一個默認的Context,即使在同一個應用域,也存在穿越不同Context的問題。NET的組件服務發展了COM+的組件服務,它使用Context Attribute來實現象COM+一樣的攔截功能。通過對調用對象的攔截,我們可以對一個方法的調用進行前處理和後處理,同時也解決了上述的跨越邊界的問題。
需要提醒你,如果你在MSDN文檔查ContextAttribute,我可以保證你得不到任何有助於了解ContextAttribute的資料,你看到的將是這麼一句話:“This type supports the .NET Framework infrastructure and is not intended to be used directly from your code.”——“本類型支持.NET Framework基礎結構,它不打算直接用於你的代碼。”不過,在msdn站點,你可以看到一些有關這方面的資料(見文章後面的參考鏈接)。
下面我們介紹有關的幾個類和一些概念,首先是:
ContextAttribute類
ContextAttribute派生自Attribute,同時它還實現了IContextAttribute和IContextProperty接口。所有自定義的ContextAttribute必須從這個類派生。
構造器:
ContextAttribute:構造器帶有一個參數,用來設置ContextAttribute的名稱。
公共屬性:
Name:只讀屬性。返回ContextAttribute的名稱
公共方法:
GetPropertiesForNewContext:虛擬方法。向新的Context添加屬性集合。
IsContextOK:虛擬方法。查詢客戶Context中是否存在指定的屬性。
IsNewContextOK:虛擬方法。默認返回true。一個對象可能存在多個Context,使用這個方法來檢查新的Context中屬性是否存在沖突。
Freeze:虛擬方法。該方法用來定位被創建的Context的最後位置。
ContextBoundObject類
實現被攔截的類,需要從ContextBoundObject類派生,這個類的對象通過Attribute來指定它所在Context,凡是進入該Context的調用都可以被攔截。該類從MarshalByRefObject派生。
以下是涉及到的接口:
IMessage:定義了被傳送的消息的實現。一個消息必須實現這個接口。
IMessageSink:定義了消息接收器的接口,一個消息接收器必須實現這個接口。還有幾個接口,我們將在下一節結合攔截構架的實現原理中進行介紹。
(待續)
(承上節) .NET Framework攔截機制的設計中,在客戶端和對象之間,存在著多種消息接收器,這些消息接收器組成一個鏈表,客戶端的調用對象的過程以及調用返回實行攔截,你可以定制自己的消息接收器,把它們插入了到鏈表中,來完成你對一個調用的前處理和後處理。那麼調用攔截是如何構架或者說如何實現的呢?
在.NET中有兩種調用,一種是跨應用域(App Domain),一種是跨上下文環境(Context),兩種調用均通過中間的代理(proxy),代理被分為兩個部分:透明代理和實際代理。透明代理暴露同對象一樣的公共入口點,當客戶調用透明代理的時候,透明代理把堆棧中的幀轉換為消息(上一節提到的實現IMessage接口的對象),消息中包含了方法名稱和參數等屬性集,然後把消息傳遞給實際代理,接下去分