Attribute在.NET编程中的应用(五)
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:定义了消息接收器的接口,一个消息接收器必须实现这个接口。
还有几个接口,我们将在下一节结合拦截构架的实现原理中进行介绍。
(待续)
参考文章:Decouple Components by Injecting Custom Services into Your Object's Interception Chain