面向方面的编程
使用 RealProxy 类进行面向方面的编程
一个架构良好的应用程序有不同的层,这样,不同的关注点不会进行不必要的交互。假设要设计松散耦合、可维护的应用程序,但在开发过程中,发现某些要求可能不适合体系结构,如:
- 在将数据写入数据库之前,必须对数据进行验证。
- 应用程序必须具备审计和日志记录功能,以进行合理的操作。
- 应用程序必须维护调试日志以检查操作是否正常。
- 必须测量某些操作的性能,以便了解这些操作是否在要求的范围内。
所有这些要求都需要大量的工作以及代码重复。您必须在系统的很多部分添加相同的代码,这样就不符合“切勿重复”(DRY) 的原则,维护也更加困难。如果要求有任何变化,都会引起对程序的大量更改。如果我必须在应用程序中添加这类内容,我会想:“为什么编译器不能为我在多个位置添加这些重复代码?”,或者“我希望我可以‘向这个方法添加日志记录’”。
值得高兴的是,确实可以做到这一点:面向方面的编程 (AOP)。它从跨对象或层的边界的方面分离出常规代码。例如,应用程序日志不绑定到任何应用程序层。它应用于整个程序,应该无所不在。这称为“横切关注点”。
根据维基百科,AOP 是“旨在通过允许分离横切关注点来提高模块化程度的编程模式”。它处理发生在系统多个部分的功能,将这种功能与应用程序核心分开,从而改进关注点的分离,避免代码重复和耦合。
本文将介绍 AOP 基础知识,然后详细说明如何通过 Microsoft .NET Framework 类 RealProxy 使用动态代理来简化这一过程。
实现 AOP
AOP 的最大优势是,您只需关注一个位置的特定方面,对其进行编程,根据需要将其应用于所有位置。AOP 有许多用途,如:
- 在应用程序中实现日志记录。
- 在操作之前使用身份验证(如仅允许经过身份验证的用户执行某些操作)。
- 为属性 setter 实现验证或通知(为实现 INotifyPropertyChanged 接口的类更改属性时,调用 PropertyChanged 事件)。
- 更改某些方法的行为。
可以看到,AOP 有许多用途,但您使用它时必须小心。它会隐藏一些代码,而代码是存在的,在相关方面的每次调用中运行。它可能会有错误,严重影响应用程序的性能。方面中的细微错误可能要耗费很多调试时间。如果方面不在很多位置使用,有时最好是直接添加到代码中。
AOP 实现会采用一些常见方法:
- 使用预处理器(如 C++ 中的预处理器)添加源代码。
- 使用后处理器在编译后的二进制代码上添加指令。
- 使用特殊编译器在编译时添加代码。
- 在运行时使用代码拦截器拦截执行并添加所需的代码。
在 .NET Framework 中,最常用的方法是后处理和代码拦截。PostSharp (postsharp.net) 使用前一方法,Castle DynamicProxy (bit.ly/JzE631) 和 Unity (unity.codeplex.com) 等依赖关系注入容器使用后一方法。这些工具通常使用称为 Decorator 或 Proxy 的设计模式来执行代码拦截。
Decorator 设计模式
Decorator 设计模式解决一个常见问题:您有一个类,需要向其添加某种功能。您有几种选择:
- 可以将新功能直接添加到类。但是,这样类要多承担一项责任,不符合“单一责任”原则。
- 您可以创建新类来执行这一功能,然后从旧类调用新类。这会带来新问题:要是还需要使用不含新功能的类,该怎么办?
- 您可以继承一个新类,添加新功能,但这样可能产生许多新的类。例如,假设您有一个用于创建、读取、更新和删除 (CRUD) 数据库操作的存储库类,您需要添加审计。后来,您需要添加数据验证以确保数据正确更新。此后,您可能还需要对访问进行身份验证,以确保只有授权用户才能访问这些类。以下是较大的问题:您可以用一些类来实现所有三个方面,可以用一些类仅实现其中两个方面甚至仅一个方面。最后,会有多少类?
- 您可以使用方面“修饰”类,从而创建一个使用方面、然后调用旧类的新类。这样,如果需要一个方面,就对它修饰一次。如果需要两个方面,就对它修饰两次,依此类推。假设您订购一个玩具(我们都是电脑高手,可以是 Xbox 或智能手机这类玩具)。它需要包装,以便在商店中展示,也可以得到保护。您订购时配上礼品包装(第二次装饰),用胶带、彩条、卡片和礼品包装纸对包装盒进行装饰。商店使用第三层包装(带泡沫聚苯乙烯球的盒子)发送玩具。玩具有三层装饰,每层装饰的功能都不同,各层装饰相互独立。购买玩具时您可以不要礼品包装,可以在商店挑选它时不要外包装盒,甚至不带盒子就买下它(有特殊折扣!)。玩具可以有任何装饰组合,但这些装饰都不会改变玩具的基本功能。
既然您已了解 Decorator 模式,我将说明如何在 C# 中实现它。
首先,创建一个接口 IRepository<T>:
public interface IRepository<T>
{
void Add(T entity);
void Delete(T entity);
void Update(T entity);
IEnumerable<T> GetAll();
T GetById(int id);
}
通过 Repository<T> 类实现它,如图 1 所示。
图 1 Repository<T> 类
public class Repository<T> : IRepository<T>
{
public void Add(T entity)
{
Console.WriteLine("Adding {0}", entity);
}
public void Delete(T entity)
{
Console.WriteLine("Deleting {0}", entity);
}
public void Update(T entity)
{
Console.WriteLine("Updating {0}", entity);
}
public IEnumerable<T> GetAll()
{
Console.WriteLine("Getting entities");
return null;
}
public T GetById(int id)
{
Console.WriteLine("Getting entity {0}", id);
return default(T);
}
}
使用 Repository<T> 类添加、更新、删除和检索 Customer 类的元素:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
程序可能如图 2 所示。
图 2 无日志记录的主程序
static void Main(string[] args)
{
Console.WriteLine("***\r\n Begin program - no logging\r\n");
IRepository<Customer> customerRepository =
new Repository<Customer>();
var customer = new Customer
{
Id = 1,
Name = "Customer 1",
Address = "Address 1"
};
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine("\r\nEnd program - no logging\r\n***");
Console.ReadLine();
}
运行这段代码时,会显示如图 3 所示的内容。
图 3 无日志记录的程序的输出
假设上级要求您向这个类添加日志记录。您可以创建一个新类对 IRepository<T> 进行修饰。它接收这个类,生成并实现同样的接口,如图 4 所示。
图 4 Logger Repository
public class LoggerRepository<T> : IRepository<T>
{
private readonly IRepository<T> _decorated;
public LoggerRepository(IRepository<T> decorated)
{
_decorated = decorated;
}
private void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg, arg);
Console.ResetColor();
}
public void Add(T entity)
{
Log("In decorator - Before Adding {0}", entity);
_decorated.Add(entity);
Log("In decorator - After Adding {0}", entity);
}
public void Delete(T entity)
{
Log("In decorator - Before Deleting {0}", entity);
_decorated.Delete(entity);
Log("In decorator - After Deleting {0}", entity);
}
public void Update(T entity)
{
Log("In decorator - Before Updating {0}", entity);
_decorated.Update(entity);
Log("In decorator - After Updating {0}", entity);
}
public IEnumerable<T> GetAll()
{
Log("In decorator - Before Getting Entities");
var result = _decorated.GetAll();
Log("In decorator - After Getting Entities");
return result;
}
public T GetById(int id)
{
Log("In decorator - Before Getting Entity {0}", id);
var result = _decorated.GetById(id);
Log("In decorator - After Getting Entity {0}", id);
return result;
}
}
这个新类对已修饰类的方法进行包装,添加日志记录功能。要调用该日志记录类,必须对代码进行略微更改,如图 5 所示。
图 5 使用 Logger Repository 的主程序
static void Main(string[] args)
{
Console.WriteLine("***\r\n Begin program - logging with decorator\r\n");
// IRepository<Customer> customerRepository =
// new Repository<Customer>();
IRepository<Customer> customerRepository =
new LoggerRepository<Customer>(new Repository<Customer>());
var customer = new Customer
{
Id = 1,
Name = "Customer 1",
Address = "Address 1"
};
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine("\r\nEnd program - logging with decorator\r\n***");
Console.ReadLine();
}
您只需创建新类,传递旧类的实例作为其构造函数的参数。在执行程序时,可以看到它有日志记录,如图 6 所示。
图 6 使用 Decorator 执行日志记录程序
您可能会认为:“想法是不错,但需要大量工作:我必须实现所有类并将方面添加到所有方法。这很难维护。有没有其他方法可以实现呢?”通过 .NET Framework,您可以使用反射来获取所有方法并加以执行。基类库 (BCL) 甚至有可用来执行该实现的 RealProxy 类 (bit.ly/18MfxWo)。
使用 RealProxy 创建动态代理
RealProxy 类提供基本代理功能。它是一个抽象类,必须通过重写其 Invoke 方法并添加新功能来继承。该类在命名空间 System.Runtime.Remoting.Proxies 中。要创建动态代理,可使用如图 7 所示的代码。
图 7 动态代理类
class DynamicProxy<T> : RealProxy
{
private readonly T _decorated;
public DynamicProxy(T decorated)
: base(typeof(T))
{
_decorated = decorated;
}
private void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg, arg);
Console.ResetColor();
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
Log("In Dynamic Proxy - Before executing '{0}'",
methodCall.MethodName);
try
{
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
Log("In Dynamic Proxy - After executing '{0}' ",
methodCall.MethodName);
return new ReturnMessage(result, null, 0,
methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
Log(string.Format(
"In Dynamic Proxy- Exception {0} executing '{1}'", e),
methodCall.MethodName);
return new ReturnMessage(e, methodCall);
}
}
}
在该类的构造函数中,必须调用基类的构造函数,传递要修饰的类的类型。然后,必须重写将接收 IMessage 参数的 Invoke 方法。它包含一个字典,字典中是为该方法传递的所有参数。IMessage 参数会类型转换为 IMethodCallMessage,这样,就可以提取参数 MethodBase(具有 MethodInfo 类型)。
接下来的步骤是在调用该方法前添加所需的方面,使用 methodInfo.Invoke 调用原始方法,调用之后添加该方面。
您不能直接调用代理,因为 DynamicProxy<T> 不是 IRepository<Customer>。这意味着您不能这样调用它:
IRepository<Customer> customerRepository =
new DynamicProxy<IRepository<Customer>>(
new Repository<Customer>());
要使用经过修饰的存储库,您必须使用 GetTransparentProxy 方法,此方法将返回 IRepository<Customer> 的实例。所调用的此实例的每个方法都将经历该代理的 Invoke 方法。为了方便实现此过程,您可以创建一个 Factory 类来创建代理并返回存储库的实例:
public class RepositoryFactory
{
public static IRepository<T> Create<T>()
{
var repository = new Repository<T>();
var dynamicProxy = new DynamicProxy<IRepository<T>>(repository);
return dynamicProxy.GetTransparentProxy() as IRepository<T>;
}
}
这样,主程序如图 8 所示。
图 8 带动态代理的主程序
static void Main(string[] args)
{
Console.WriteLine("***\r\n Begin program - logging with dynamic proxy\r\n");
// IRepository<Customer> customerRepository =
// new Repository<Customer>();
// IRepository<Customer> customerRepository =
// new LoggerRepository<Customer>(new Repository<Customer>());
IRepository<Customer> customerRepository =
RepositoryFactory.Create<Customer>();
var customer = new Customer
{
Id = 1,
Name = "Customer 1",
Address = "Address 1"
;
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine("\r\nEnd program - logging with dynamic proxy\r\n***");
Console.ReadLine();
}
在执行此程序时,结果和前面类似,如图 9 所示。
图 9 使用动态代理的程序执行
可以看到,您已创建一个动态代理,可将方面添加到代码,无需重复该操作。如果要添加一个新方面,只需创建一个新类,从 RealProxy 继承,用它来修饰第一个代理。
如果上级又要求您向代码中添加授权,以便只有管理员才能访问存储库,则可以创建一个新代理,如图 10 所示。
图 10 身份验证代理
class AuthenticationProxy<T> : RealProxy
{
private readonly T _decorated;
public AuthenticationProxy(T decorated)
: base(typeof(T))
{
_decorated = decorated;
}
private void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(msg, arg);
Console.ResetColor();
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
if (Thread.CurrentPrincipal.IsInRole("ADMIN"))
{
try
{
Log("User authenticated - You can execute '{0}' ",
methodCall.MethodName);
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
return new ReturnMessage(result, null, 0,
methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
Log(string.Format(
"User authenticated - Exception {0} executing '{1}'", e),
methodCall.MethodName);
return new ReturnMessage(e, methodCall);
}
}
Log("User not authenticated - You can't execute '{0}' ",
methodCall.MethodName);
return new ReturnMessage(null, null, 0,
methodCall.LogicalCallContext, methodCall);
}
}
必须更改存储库工厂才能调用两个代理,如图 11 所示。
图 11 由两个代理修饰的存储库工厂
public class RepositoryFactory
{
public static IRepository<T> Create<T>()
{
var repository = new Repository<T>();
var decoratedRepository =
(IRepository<T>)new DynamicProxy<IRepository<T>>(
repository).GetTransparentProxy();
// Create a dynamic proxy for the class already decorated
decoratedRepository =
(IRepository<T>)new AuthenticationProxy<IRepository<T>>(
decoratedRepository).GetTransparentProxy();
return decoratedRepository;
}
}
如果将主程序更改为如图 12 所示,然后运行,则输出如图 13 所示。
图 12 使用两个用户调用存储库的主程序
static void Main(string[] args)
{
Console.WriteLine(
"***\r\n Begin program - logging and authentication\r\n");
Console.WriteLine("\r\nRunning as admin");
Thread.CurrentPrincipal =
new GenericPrincipal(new GenericIdentity("Administrator"),
new[] { "ADMIN" });
IRepository<Customer> customerRepository =
RepositoryFactory.Create<Customer>();
var customer = new Customer
{
Id = 1,
Name = "Customer 1",
Address = "Address 1"
};
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine("\r\nRunning as user");
Thread.CurrentPrincipal =
new GenericPrincipal(new GenericIdentity("NormalUser"),
new string[] { });
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine(
"\r\nEnd program - logging and authentication\r\n***");
Console.ReadLine();
}
图 13 使用两个代理的程序的输出
程序执行两次存储库方法。第一次,它以管理员用户身份运行,并调用这些方法。第二次,它以普通用户身份运行,并跳过这些方法。
这要容易很多,对吧?请注意,工厂返回 IRepository<T> 的实例,因此,程序不知道它是否正在使用经过修饰的版本。这符合里氏替换原则,即如果 S 是 T 的子类型,则类型 T 的对象可以替换为类型 S 的对象。这种情况下,通过使用 IRepository<Customer> 接口,可以使用可实现此接口的任何类而不必更改程序。
筛选功能
到现在为止,函数中没有筛选;该方面应用于所调用的每个类方法。这通常不是所希望的行为。例如,您可能不需要记录检索方法(GetAll 和 GetById)。要实现这一点,一种方式是按名称对该方面进行筛选,如图 14 所示。
图 14 针对方面筛选方法
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
if (!methodInfo.Name.StartsWith("Get"))
Log("In Dynamic Proxy - Before executing '{0}'",
methodCall.MethodName);
try
{
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
if (!methodInfo.Name.StartsWith("Get"))
Log("In Dynamic Proxy - After executing '{0}' ",
methodCall.MethodName);
return new ReturnMessage(result, null, 0,
methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
if (!methodInfo.Name.StartsWith("Get"))
Log(string.Format(
"In Dynamic Proxy- Exception {0} executing '{1}'", e),
methodCall.MethodName);
return new ReturnMessage(e, methodCall);
}
}
程序检查方法是否以“Get”开头。如果是,则程序不应用该方面。这样是可行的,但是要重复三次筛选代码。此外,筛选器在代理内,这样,每次要更改代理都需要更改该类。通过创建 IsValidMethod 谓词可以对此进行改进:
private static bool IsValidMethod(MethodInfo methodInfo)
{
return !methodInfo.Name.StartsWith("Get");
}
现在,您只需在一处更改,但是每次更改筛选器时都需要更改该类。一个解决方法是将筛选器公开为类属性,这样可以将创建筛选器的责任分配给调用方。您可以创建类型 Predicate<MethodInfo> 的 Filter 属性,用它来筛选数据,如图 15 所示。
图 15 筛选代理
class DynamicProxy<T> : RealProxy
{
private readonly T _decorated;
private Predicate<MethodInfo> _filter;
public DynamicProxy(T decorated)
: base(typeof(T))
{
_decorated = decorated;
_filter = m => true;
}
public Predicate<MethodInfo> Filter
{
get { return _filter; }
set
{
if (value == null)
_filter = m => true;
else
_filter = value;
}
}
private void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg, arg);
Console.ResetColor();
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
if (_filter(methodInfo))
Log("In Dynamic Proxy - Before executing '{0}'",
methodCall.MethodName);
try
{
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
if (_filter(methodInfo))
Log("In Dynamic Proxy - After executing '{0}' ",
methodCall.MethodName);
return new ReturnMessage(result, null, 0,
methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
if (_filter(methodInfo))
Log(string.Format(
"In Dynamic Proxy- Exception {0} executing '{1}'", e),
methodCall.MethodName);
return new ReturnMessage(e, methodCall);
}
}
}
Filter 属性可通过 Filter = m => true 初始化。这意味着没有处于活动状态的筛选器。在为 Filter 属性赋值时,程序会验证值是否为 null,如果是,则程序重置筛选器。在 Invoke 方法执行中,程序会检查筛选结果,如果结果为 true,则程序应用该方面。现在,工厂类中的代理创建如下所示:
public class RepositoryFactory
{
public static IRepository<T> Create<T>()
{
var repository = new Repository<T>();
var dynamicProxy = new DynamicProxy<IRepository<T>>(repository)
{
Filter = m => !m.Name.StartsWith("Get")
};
return dynamicProxy.GetTransparentProxy() as IRepository<T>;
}
}
}
创建筛选器的责任已转移给工厂。运行程序时,结果如图 16 所示。
图 16 使用筛选代理时的输出
请注意,在图 16 中,最后两个方法(GetAll 和 GetById,由“Getting entities”和“Getting entity 1”表示)没有日志记录。通过将这些方面公开为事件,可以进一步增强该类。这样,不必在每次需要更改该方面时都更改该类。如图 17 所示。
图 17 灵活代理
class DynamicProxy<T> : RealProxy
{
private readonly T _decorated;
private Predicate<MethodInfo> _filter;
public event EventHandler<IMethodCallMessage> BeforeExecute;
public event EventHandler<IMethodCallMessage> AfterExecute;
public event EventHandler<IMethodCallMessage> ErrorExecuting;
public DynamicProxy(T decorated)
: base(typeof(T))
{
_decorated = decorated;
Filter = m => true;
}
public Predicate<MethodInfo> Filter
{
get { return _filter; }
set
{
if (value == null)
_filter = m => true;
else
_filter = value;
}
}
private void OnBeforeExecute(IMethodCallMessage methodCall)
{
if (BeforeExecute != null)
{
var methodInfo = methodCall.MethodBase as MethodInfo;
if (_filter(methodInfo))
BeforeExecute(this, methodCall);
}
}
private void OnAfterExecute(IMethodCallMessage methodCall)
{
if (AfterExecute != null)
{
var methodInfo = methodCall.MethodBase as MethodInfo;
if (_filter(methodInfo))
AfterExecute(this, methodCall);
}
}
private void OnErrorExecuting(IMethodCallMessage methodCall)
{
if (ErrorExecuting != null)
{
var methodInfo = methodCall.MethodBase as MethodInfo;
if (_filter(methodInfo))
ErrorExecuting(this, methodCall);
}
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
OnBeforeExecute(methodCall);
try
{
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
OnAfterExecute(methodCall);
return new ReturnMessage(
result, null, 0, methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
OnErrorExecuting(methodCall);
return new ReturnMessage(e, methodCall);
}
}
}
在图 17 中,BeforeExecute、AfterExecute 和 ErrorExecuting 这三个事件由方法 OnBeforeExecute、OnAfterExecute 和 OnErrorExecuting 调用。这些方法验证是否定义了事件处理程序,如果已定义,则检查所调用的方法是否通过筛选器。如果是,则调用应用该方面的事件处理程序。该工厂类现在如图 18 所示。
图 18 用于设置方面事件和筛选器的存储库工厂
public class RepositoryFactory
{
private static void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg, arg);
Console.ResetColor();
}
public static IRepository<T> Create<T>()
{
var repository = new Repository<T>();
var dynamicProxy = new DynamicProxy<IRepository<T>>(repository);
dynamicProxy.BeforeExecute += (s, e) => Log(
"Before executing '{0}'", e.MethodName);
dynamicProxy.AfterExecute += (s, e) => Log(
"After executing '{0}'", e.MethodName);
dynamicProxy.ErrorExecuting += (s, e) => Log(
"Error executing '{0}'", e.MethodName);
dynamicProxy.Filter = m => !m.Name.StartsWith("Get");
return dynamicProxy.GetTransparentProxy() as IRepository<T>;
}
}
现在有了一个灵活代理类,您可以选择要在执行之前、执行之后或在发生错误时仅针对所选方法应用的方面。这样,可以方便地在代码中不重复地应用许多方面。
并非替换
通过 AOP,您可以集中向应用程序的所有层添加代码,无需重复代码。我说明了如何基于 Decorator 设计模式来创建将方面应用到使用事件的类的泛型动态代理,并创建用于筛选所需函数的谓词。
可以看到,RealProxy 类是一个灵活类,可用来全面控制代码而无需外部依赖关系。但是请注意,RealProxy 并不是用来替换其他 AOP 工具,如 PostSharp。PostSharp 使用完全不同的方法。它在编译后步骤中添加中间语言 (IL) 代码,不使用反射,因此它与 RealProxy 相比具有更好的性能。相比 PostSharp,要使用 RealProxy 实现某个方面,您还必须完成更多工作。通过 PostSharp,您只需要创建方面类,向要添加该方面的类(或方法)添加属性,就这么简单。
另一方面,通过 RealProxy,无需任何外部依赖关系就可以全面控制源代码,可根据自己的需要进行扩展和自定义。例如,如果只需要对具有 Log 属性的方法应用某个方面,则可以执行如下操作:
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
if (!methodInfo.CustomAttributes
.Any(a => a.AttributeType == typeof (LogAttribute)))
{
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
return new ReturnMessage(result, null, 0,
methodCall.LogicalCallContext, methodCall);
}
...
此外,RealProxy 所使用的方法(拦截代码并允许程序将其替换)功能十分强大。例如,如果需要创建一个模拟框架,以创建测试用的泛型模拟和存根,则可以使用 RealProxy 类来拦截所有调用,并替换为您自己的行为,不过,这将在另一篇文章中介绍!
Bruno Sonnino 是 Microsoft 最有价值专家 (MVP),居住在巴西。他是开发人员、顾问和作者,著有五部有关 Delphi 的书籍(由 Pearson Education Brazil 出版,葡萄牙语),并为巴西和美国的杂志和网站撰写了许多文章。
衷心感谢以下 Microsoft Research 技术专家对本文的审阅:James McCaffrey、Carlos Suarez 和 Johan Verwey
James McCaffrey 供职于华盛顿州雷德蒙德市的 Microsoft Research。他参与过多个 Microsoft 产品的工作,包括 Internet Explorer 和 Bing。可通过 jammc@microsoft.com 与他联系。
Carlos Garcia Jurado Suarez 是 Microsoft Research 的研究软件工程师,他曾在高级开发团队 (Advanced Development Team) 工作,最近在机器学习小组 (Machine Learning Group) 工作。早些时候,他是 Visual Studio 开发人员,从事建模工具(如 Class Designer)的开发。