[
Namespace]:=http://。。。。。/TheAgileDeveloper.ContactService/Service1 _ )> _
Public Class Contact
'
Public Id As Integer
'
Public FirstName As String
'
Public LastName As String
'
Public WebSite As String
End Class
注意,Contact
对象只处理数据,而且我们不想以任何方式编辑该代码,因为 wsdl.exe 会为我们自动生成,所以下一次生成时更改将丢失。我想引入行为,这样就能够通过调用名为 Save 的方法保存对象,这很容易通过一个混入 来完成。混入 是多继承的翻版,只是它有局限性,例如只能混入接口实现。我们使用的 Encase 框架包含一个 Encaser 类,它负责接收并包装一个对象。包装
对象的行为实际上意味着创建新的对象,在本例中就是新的 Contact 对象,它包含配置的混入和切点。
为了创建允许在 Contact对象上调用 Save 方法的混入,需要指定一个接口,我称之为 ISavable。实际混入对象的就是 ISavable 接口。我们需要在另一个称为 ContactSave 的新类中实现该接口。
Public Interface ISaveable
Sub Save()
End Interface
Public Class ContactSave
Implements ISavable
Public Contact As ContactService.Contact
Public Sub Save() Implements ISavable.Save
ServiceManager.SaveContact(Me.Contact)
End Sub
End Class
在我们的应用程序中,混入 Contact
对象中 ContactSave 实现的适当位置是 ServiceManager。我们能够混入这个行为,但是不更改任何客户端代码(即,MainForm),因为应用混入后,结合 Contact 和 ContactSave 的新 Contact对象仍然保持为最初的 Contact 类型。以下代码是经过更改的 ServiceManager 的 GetAllContacts 方法,它处理混入行为。
Public Shared Function GetAllContacts() As ContactService.Contact()
Dim service As ContactService.Service = New ContactService.Service
Dim contacts() As ContactService.Contact = service.GetAllContacts
'//Wrap each contact object
For i As Integer = 0 To contacts.Length-1
'//Create a new instance of the
'//encaser responsible for wrapping our object
Dim encaser As encaser = New encaser
'//Add mixin instance of ContactSave
Dim saver As ContactSave = New ContactSave
encaser.AddMixin(saver)
'//Creates a new object with
'//Contact and ContactSave implementations
Dim wrappedObject As Object = encaser.Wrap(contacts(i))
'//Assign our new wrapped contact object
'//to the previous contact object
contacts(i) = DirectCast(wrappedObject, _
ContactService.Contact)
'//Notice the wrapped object is still the same type
'//Assign the new wrapped Contact object to
'//target field of the ContactSave mixed in
saver.Target = contacts(i)
Next
Return contacts
End Function
软件特点
每个框架应用切点、通知或方面的方法都是独特的,但是其目的和概念是相同的。在本文示例中,Encaser 包装一个
对象时真正进行的操作是,通过 System.Reflection.Emit
命名空间中的类产生 MSIL 代码,从而随时创建新的 Contact 类型。新 Contact 类型派生于 Contact 类,它仍然共享类型,但是新包装的对象还持有对 ContactSave 对象的引用,后者是我们混入的。ISavable.Save 方法在新的 Contact对象上实现,因此在调用 Save 时,它实际上将
调用委托给混入的 ContactSave 对象。这样做的优点是能够将新的 Contact
对象转换为在任何混入对象上实现的任何接口。
图 2. 包装对象的 UML 图表。
您或许在想,通过 .NET Framework 2.0 的部分类语言功能,可以在另一个 partial 类中添加 Save 行为。这是可能实现的,但是本文没有采用这种方法,这是为了使代码与 .NET Framework 1.x 的其他版本
向后兼容。既然有部分语言功能,那么在正常情况下,前面的示例也就不需要使用混入 了。但是混入 仍然很有价值,因为通过它,开发人员可以混入可重用的
对象行为,这些对象可以源自其他不相关的对象层次结构,它实现的功能比 partial 类更多。在使用 partial 关键字时,是在同一个类或类型中添加代码,只不过物理位置不同。下一个混入示例说明的添加行为不只特定于 Contact 类,而是一个名为 FieldUndoer 的可重用类。FieldUndoer 实现了 IUndoable 接口,允许已修改的对象恢复为原来的状态。
Public Interface IUndoable
ReadOnly Property HasChanges() As Boolean
Sub Undo()
Sub AcceptChanges()
End Interface
HasChanges 属性表示,如果发生了更改,Undo 将
对象恢复为原来的状态,AcceptChanges 接收对象的当前更改,因此任何时候再调用 Undo 时都会恢复为上一次接收更改的状态。如果该接口是在一个部分类中实现的,那么在每个希望包含该行为的类中,都必须不厌其烦地重复实现这三个方法。作为一个实用主义编程人员,我尝试坚持“
一次且仅一次代码”原则,所以我永远不想重复任何代码,复制和粘贴越少越好。通过使用混入,我能够重用实现 IUndoable 的 FieldUndoer
对象。在 ServiceManager 中我又混入了这个新功能。所有客户端代码仍然不知道新的混入,而且也不需要更改,除非需要使用 IUndoable 接口。更改 MainForm 中的 Contact对象,然后单击“撤消”,测试这个行为。
Public Shared Function GetAllContacts() As ContactService.Contact()
Dim service As ContactService.Service = New ContactService.Service
Dim contacts() As ContactService.Contact = service.GetAllContacts
'//Wrap each contact object
For i As Integer = 0 To contacts.Length-1
'//Create a new instance of the encaser
'//responsible for wrapping our object
Dim encaser As encaser = New encaser
'//Add mixin instance of ContactSave
Dim saver As ContactSave = New ContactSave
encaser.AddMixin(saver)
'//Add mixin instance of FieldUndoer
Dim undoer As FieldUndoer = New FieldUndoer
encaser.AddMixin(undoer)
'//Creates a new object with Contact
'//and ContactSave implementations
Dim wrappedObject As Object = encaser.Wrap(contacts(i))
'//Assign our new wrapped contact object
'//to the previous contact object
contacts(i) = DirectCast(wrappedObject, _
ContactService.Contact)
'//Notice the wrapped object is still the same type
'//Assign the new wrapped Contact object to target fields
saver.Target = contacts(i)
undoer.Target = contacts(i)
Next
Return contacts
End Function
组合行为
混入还只是冰山一角。真正让 AOP 声名鹊起的功能是组合混入行为。以使用新 Contact
对象为例,在调用 ISavable.Save 方法时,客户端代码还需要调用 IUndoable.AcceptChanges 方法,以便在下一次调用 IUndoable.Undo 时恢复到所保存的上一次更改。在这个小的 MainForm 中浏览和添加该对象很容易,但是在任何比用户界面大得多的系统中对该规则编码将是一项繁重的任务。您需要查找所有调用 Save 方法的情况,然后添加另一个对 AcceptChanges 的调用。而且在创建新代码的过程中,开发人员也需要牢记,在每次调用 Save 时都添加这个新功能。这很快就会产生级联效应,很容易会破坏系统稳定姓,引入一些难于跟踪的 bug。而使用面向方面的编程则能够组合这些方法。指定一个切点和通知,在调用 Save 方法时,Contact
对象将自动调用后台的 AcceptChanges。
为了在应用程序中实现组合,需要在 ServiceManager 中再添加一行代码。我们在加入 FieldUndoer 混入后添加这行代码。
'//Specify join point save, execute the AcceptChanges method
AddPointcut 方法通过几个不同的签名进行
重载,这为指定切点提供了更大的灵活性。我们调用的 AddPointcut 接收了一个字符串类型的接合点名,它表示为 Save 方法,然后又接收了一个名为 AcceptChanges 的方法作为执行的通知。要查看这是否起作用,可以分别在 FieldUndoer.AcceptChanges 方法和 ContactSave.Save 方法前设置一个断点。单击 MainForm 上的 Save 按钮将截获接合点,您首先将中断至通知 — 即 AcceptChanges 方法。通知执行后将执行 Save 方法。
这个简单的示例说明如何添加贯穿整个应用程序的新行为,其功能强大无比。尽管有此功能,但它不仅仅是添加功能的一种很好的新方法。在众多优点中,只有几个涉及代码重用,以及通过简化新需求带来的系统进化来改进系统的
可维护性。与此同时,误用 AOP 会对系统的可维护性造成显著的负面效应,因此了解使用 AOP 的时机和方法很重要。
AOP 走了多远?
将 AOP 用于多数大型系统或关键的
生产系统还不完全成熟,但是随着语言支持的提高,AOP 的应用将更容易。另外,提高支持也是新的软件开发范例,例如利用面向方面的编程的
软件工厂。在 .NET 领域中有几种可用的 AOP 框架,每个框架都有其自己的方法、正面属性和负面属性。
Encase — 本代码示例中的 Encase 框架只是一个工具,帮助您快速了解并运行 AOP,以及理解 AOP 背后的概念。Encase 在运行时期间应用能够单独添加到
对象的方面。
Aspect# — 一个针对 CLI 的 AOP 联合兼容框架,提供声明和配置方面的内置语言。
RAIL — RAIL 框架在
虚拟机JIT 类时应用方面。
Spring.。。。 — 流行的 Java Spring 框架的一个 .NET 版本。在下一个版本中将实现 AOP。
Eos — 用于 C# 的一个面向方面的扩展。
小结
本文的目的是说明一种比常规日志记录或安全实例更实用的应用
AOP 的新方法。正确应用使用 AOP 会带来很多优点,甚至能够帮助您完成常规编程选项所不能完成的成果任务。我强烈推荐您在 internet 上搜寻大量可用资源,以指导应用 AOP 的方法和场景时机。