青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

C++ Programmer's Cookbook

{C++ 基礎} {C++ 高級} {C#界面,C++核心算法} {設計模式} {C#基礎}

面向方面的編程

面向方面的編程

發布日期: 12/26/2005 | 更新日期: 12/26/2005

Matthew Deiters
ThoughtWorks

適用于:
Microsoft Visual Studio
Microsoft Visual Basic

摘要:從實際應用的角度考察面向方面的編程,說明如何動態擴展 Web 服務客戶端應用程序中的行為。

單擊此處可下載本文的代碼示例。

*
本頁內容
簡介 簡介
什么是方面? 什么是方面?
AOP:利與弊 AOP:利與弊
超越 Trace.WriteLine 超越 Trace.WriteLine
混入的后臺實現 混入的后臺實現
組合行為 組合行為
AOP 走了多遠? AOP 走了多遠?
小結 小結

簡介

面向方面的編程 (AOP) 由來已久,但是直到最近才開始獲得 Microsoft .NET 開發社區的青睞。任何一項新技術的采納往往都會產生對該技術及其使用的誤解,AOP 也不例外。為了澄清對 AOP 的誤解,本文以及下列代碼示例將舉例說明一個 AOP 的實際應用程序和一些 AOP 能夠解決的常見問題。以使用 Web 服務的應用程序為例,我們將擴展該 Web 服務返回的對象功能,方法是通過一個 AOP 框架對返回的對象應用新的方面。這些方面將為此功能獨立生成對象模型,從而脫離 WSDL。

什么是方面?

在考慮對象及對象與其他對象的關系時,我們通常會想到繼承這個術語。例如,定義某一個抽象類 — Dog 類。在標識相似的一些類但每個類又有各自的獨特行為時,通常使用繼承來擴展功能。舉例來說,如果標識了 Poodle,則可以說一個 Poodle 是一個 Dog,即 Poodle 繼承了 Dog。到此為止都似乎不錯,但是如果定義另一個以后標識為 Obedient Dog 的獨特行為又會怎樣呢?當然,不是所有的 Dogs 都很馴服,所以 Dog 類不能包含 obedience 行為。此外,如果要創建從 Dog 繼承的 Obedient Dog 類,那么 Poodle 放在這個層次結構中的哪個位置合適呢?Poodle 是一個 Dog,但是 Poodle 不一定 obedient;那么 Poodle 是繼承于 Dog 還是 Obedient Dog 呢?都不是,我們可以將馴服看作一個方面,將其應用到任何一類馴服的 Dog,我們反對以不恰當的方式強制將該行為放在 Dog 層次結構中。

在軟件術語中,面向方面的編程能夠獨立于任何繼承層次結構而應用改變類或對象行為的方面。然后,在運行時或編譯時應用這些方面。舉一個關于 AOP 的示例,然后進行描述,說明起來比較容易。首先,定義四個關鍵的 AOP 術語,這很重要,因為我將反復使用它們:

?

接合點 (Joinpoint) — 代碼中定義明確的可識別的點。

?

切點 (Pointcut) — 通過配置或編碼指定接合點的一種方法。

?

通知 (Advice) — 表示需要執行交叉切割動作的一種方法

?

混入 (Mixin) — 通過將一個類的實例混入目標類的實例引入新行為。

為了更好地理解這些術語,可以將接合點看作程序流中定義好的一點。說明接合點的一個很好的示例是:在代碼調用一個方法時,發生調用的那一點被認為是一個接合點。切點用于指定或定義希望在程序流中截獲的接合點。切點還包含一個通知,該通知在到達接合點時發生。因此,如果在一個調用的特定方法上定義一個切點,那么在調用該方法或接合點時,AOP 框架將截獲該切點,同時還將執行切點的通知。通知有幾種類型,但是最常見的情況是將其看作要調用的另一個方法。在調用一個帶有切點的方法時,要執行的通知將是另一個要調用的方法。要調用的這個通知或方法可以是對象中被截獲的方法,也可以是混入的另一個對象中的方法。我們將在后面進一步解釋混入。

AOP:利與弊

一種常見的誤解是認為 AOP 是截獲,事實并非如此。但是,它確實運用了截獲來應用通知以及組合行為。有一些 .NET 代碼示例通過 ContextBoundObject 以一種 AOP 翻版風格說明截獲。可是用 ContextBoundObject 來說明截獲并不合適,因為使用這種方法的先決條件是所有需要進行截獲的類都必須從 ContextBoundObject 繼承。像 ContextBoundObject 這樣帶有先決條件的 AOP 方法會帶來需求產生的負面影響,所以在 AOP 中被視為重方法,應該避免使用。重方法在系統中遺留的大量“足跡”會潛在地影響每個類,阻礙將來更改或修改系統的功能。

我創建了一個名為 Encase 的輕量型框架。用“輕量型”這個術語的意義是整體上對系統沒有影響。系統的不同部分仍然受 AOP 影響,但是選擇輕量型框架并應用良好的編程實踐可以減輕大部分負面問題。Encase 框架的用途是簡化切點、混入和方面組合。開發人員能夠通過代碼在 Encase 中應用方面,從而代替大多數其他輕量型 AOP 框架使用的配置文件(例如 XML)。

重量型框架阻礙了 AOP 的應用,但是防礙 AOP 廣泛應用的罪魁禍首是目前可用的 AOP 示例幾乎都都包含以下內容:執行方法前先截獲,并應用執行 Trace.WriteLine("Method entered.") 的方面。與普遍看法相反,除了日志記錄、安全、規范以及這類性質的事情外,AOP 對于解決其他問題也很有用。

超越 Trace.WriteLine

為了說明更實用的使用 AOP 的方法,我們將創建一個應用程序,從名為 ContactService.Service 的 Web 服務接收 people 對象的集合。目前,在 .NET 開發中使用 Web 服務的最常見方法是調用返回 XML 的 Web 服務,該服務通過框架自動反序列化為一個對象。這些對象僅包含數據而不包含任何行為。在 .NET Framework 2.0 中,通過使用 partial 關鍵字并創建行為,能夠對這些自動代碼生成的對象添加功能。但是在一些 Web 服務或代理對象之間重用某個特定行為時仍然存在一個問題。如前所述,多數情況下,共享的公共行為將包含在一個抽象類中,其他所有類從該類繼承。但是,我們不能使 Web 服務對象繼承功能。借此良機,通過這個問題說明 AOP 功能如何強大。

我們的應用程序用于顯示聯系人信息。最初它的用途是顯示信息,但是現在需要添加某些行為。為了查看代碼示例,我們需要創建一個稱為 TheAgileDeveloper.ContactService 的虛擬目錄。該目錄必須指向 TheAgileDeveloper.ContactService 項目在本地計算機上的位置。

通過 http://localhost/TheAgileDeveloper.ContactService 可以訪問此項目,這一點很重要。


1. 應用程序屏幕快照。

應用程序有一個視圖,它是一個名為 MainForm 的 WinForm,用于顯示左側 ListView 中 Web 服務返回的聯系人對象。選定一個聯系人時,名字、姓氏和 Web 頁將顯示在右側的文本框中。載入 MainForm 時,它調用 ServiceManager 類來獲取聯系人信息。下列 ServiceManager 類乍看起來似乎沒有添加任何值,只不過在窗體和 Web 服務之間添加了另一層。但是,它的價值就在于提供了一個在 Web 服務中添加新功能的位置,而不用重復代碼。另一個優點是,它將 Web 服務的“足跡”抽象出來,并從整個應用程序中移除出去。

Public Class ServiceManager

    Public Shared Function GetAllContacts() As ContactService.Contact()
        Dim service As ContactService.Service = New ContactService.Service
        Dim contacts() As ContactService.Contact = service.GetAllContacts
        Return contacts
    End Function

    Public Shared Sub SaveContact(ByVal contact As ContactService.Contact)
        Dim service As ContactService.Service = New ContactService.Service
        service.SaveContact(contact)
    End Sub

End Class

請查看 TheAgileDeveloper.Client 項目中的 Reference.vb 文件。它是在導入 ContactService 的 Web 引用時通過 wsdl.exe 創建的。它從 WSDL 自動生成以下 Contact 類。

'<remarks/>
    <System.Xml.Serialization.XmlTypeAttribute(_  
[Namespace]:=http://tempuri.org/TheAgileDeveloper.ContactService/Service1 _ )>  _
    Public Class Contact
        
        '<remarks/>
        Public Id As Integer
        
        '<remarks/>
        Public FirstName As String
        
        '<remarks/>
        Public LastName As String
        
        '<remarks/>
        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),因為應用混入后,結合 ContactContactSave 的新 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 時都會恢復為上一次接收更改的狀態。如果該接口是在一個部分類中實現的,那么在每個希望包含該行為的類中,都必須不厭其煩地重復實現這三個方法。作為一個實用主義編程人員,我嘗試堅持“一次且僅一次代碼”原則,所以我永遠不想重復任何代碼,復制和粘貼越少越好。通過使用混入,我能夠重用實現 IUndoableFieldUndoer 對象。在 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
encaser.AddPointcut("Save", "AcceptChanges") 

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.NET — 流行的 Java Spring 框架的一個 .NET 版本。在下一個版本中將實現 AOP。

?

Eos — 用于 C# 的一個面向方面的擴展。

小結

本文的目的是說明一種比常規日志記錄或安全實例更實用的應用 AOP 的新方法。正確應用使用 AOP 會帶來很多優點,甚至能夠幫助您完成常規編程選項所不能完成的成果任務。我強烈推薦您在 internet 上搜尋大量可用資源,以指導應用 AOP 的方法和場景時機。

關于作者

Matthew Deiters 對于軟件開發工作充滿熱情,他是 ThoughtWorks 的一名咨詢人員。他曾協助通過 .NET Framework 開發一些針對金融和保險行業的企業級系統。他看重 XP 編程和 TTD 方法論,認為大多數人為問題能夠通過設計模式和/或良好的單元測試解決。您可以通過 Matthew 的個人 Web 空間與他聯系:www.theAgileDeveloper.com

轉到原英文頁面


面向方面的編程

發布日期: 12/26/2005 | 更新日期: 12/26/2005

Matthew Deiters
ThoughtWorks

適用于:
Microsoft Visual Studio
Microsoft Visual Basic

摘要:從實際應用的角度考察面向方面的編程,說明如何動態擴展 Web 服務客戶端應用程序中的行為。

單擊此處可下載本文的代碼示例。

*
本頁內容
簡介 簡介
什么是方面? 什么是方面?
AOP:利與弊 AOP:利與弊
超越 Trace.WriteLine 超越 Trace.WriteLine
混入的后臺實現 混入的后臺實現
組合行為 組合行為
AOP 走了多遠? AOP 走了多遠?
小結 小結

簡介

面向方面的編程 (AOP) 由來已久,但是直到最近才開始獲得 Microsoft .NET 開發社區的青睞。任何一項新技術的采納往往都會產生對該技術及其使用的誤解,AOP 也不例外。為了澄清對 AOP 的誤解,本文以及下列代碼示例將舉例說明一個 AOP 的實際應用程序和一些 AOP 能夠解決的常見問題。以使用 Web 服務的應用程序為例,我們將擴展該 Web 服務返回的對象功能,方法是通過一個 AOP 框架對返回的對象應用新的方面。這些方面將為此功能獨立生成對象模型,從而脫離 WSDL。

什么是方面?

在考慮對象及對象與其他對象的關系時,我們通常會想到繼承這個術語。例如,定義某一個抽象類 — Dog 類。在標識相似的一些類但每個類又有各自的獨特行為時,通常使用繼承來擴展功能。舉例來說,如果標識了 Poodle,則可以說一個 Poodle 是一個 Dog,即 Poodle 繼承了 Dog。到此為止都似乎不錯,但是如果定義另一個以后標識為 Obedient Dog 的獨特行為又會怎樣呢?當然,不是所有的 Dogs 都很馴服,所以 Dog 類不能包含 obedience 行為。此外,如果要創建從 Dog 繼承的 Obedient Dog 類,那么 Poodle 放在這個層次結構中的哪個位置合適呢?Poodle 是一個 Dog,但是 Poodle 不一定 obedient;那么 Poodle 是繼承于 Dog 還是 Obedient Dog 呢?都不是,我們可以將馴服看作一個方面,將其應用到任何一類馴服的 Dog,我們反對以不恰當的方式強制將該行為放在 Dog 層次結構中。

在軟件術語中,面向方面的編程能夠獨立于任何繼承層次結構而應用改變類或對象行為的方面。然后,在運行時或編譯時應用這些方面。舉一個關于 AOP 的示例,然后進行描述,說明起來比較容易。首先,定義四個關鍵的 AOP 術語,這很重要,因為我將反復使用它們:

?

接合點 (Joinpoint) — 代碼中定義明確的可識別的點。

?

切點 (Pointcut) — 通過配置或編碼指定接合點的一種方法。

?

通知 (Advice) — 表示需要執行交叉切割動作的一種方法

?

混入 (Mixin) — 通過將一個類的實例混入目標類的實例引入新行為。

為了更好地理解這些術語,可以將接合點看作程序流中定義好的一點。說明接合點的一個很好的示例是:在代碼調用一個方法時,發生調用的那一點被認為是一個接合點。切點用于指定或定義希望在程序流中截獲的接合點。切點還包含一個通知,該通知在到達接合點時發生。因此,如果在一個調用的特定方法上定義一個切點,那么在調用該方法或接合點時,AOP 框架將截獲該切點,同時還將執行切點的通知。通知有幾種類型,但是最常見的情況是將其看作要調用的另一個方法。在調用一個帶有切點的方法時,要執行的通知將是另一個要調用的方法。要調用的這個通知或方法可以是對象中被截獲的方法,也可以是混入的另一個對象中的方法。我們將在后面進一步解釋混入。

AOP:利與弊

一種常見的誤解是認為 AOP 是截獲,事實并非如此。但是,它確實運用了截獲來應用通知以及組合行為。有一些 .NET 代碼示例通過 ContextBoundObject 以一種 AOP 翻版風格說明截獲。可是用 ContextBoundObject 來說明截獲并不合適,因為使用這種方法的先決條件是所有需要進行截獲的類都必須從 ContextBoundObject 繼承。像 ContextBoundObject 這樣帶有先決條件的 AOP 方法會帶來需求產生的負面影響,所以在 AOP 中被視為重方法,應該避免使用。重方法在系統中遺留的大量“足跡”會潛在地影響每個類,阻礙將來更改或修改系統的功能。

我創建了一個名為 Encase 的輕量型框架。用“輕量型”這個術語的意義是整體上對系統沒有影響。系統的不同部分仍然受 AOP 影響,但是選擇輕量型框架并應用良好的編程實踐可以減輕大部分負面問題。Encase 框架的用途是簡化切點、混入和方面組合。開發人員能夠通過代碼在 Encase 中應用方面,從而代替大多數其他輕量型 AOP 框架使用的配置文件(例如 XML)。

重量型框架阻礙了 AOP 的應用,但是防礙 AOP 廣泛應用的罪魁禍首是目前可用的 AOP 示例幾乎都都包含以下內容:執行方法前先截獲,并應用執行 Trace.WriteLine("Method entered.") 的方面。與普遍看法相反,除了日志記錄、安全、規范以及這類性質的事情外,AOP 對于解決其他問題也很有用。

超越 Trace.WriteLine

為了說明更實用的使用 AOP 的方法,我們將創建一個應用程序,從名為 ContactService.Service 的 Web 服務接收 people 對象的集合。目前,在 .NET 開發中使用 Web 服務的最常見方法是調用返回 XML 的 Web 服務,該服務通過框架自動反序列化為一個對象。這些對象僅包含數據而不包含任何行為。在 .NET Framework 2.0 中,通過使用 partial 關鍵字并創建行為,能夠對這些自動代碼生成的對象添加功能。但是在一些 Web 服務或代理對象之間重用某個特定行為時仍然存在一個問題。如前所述,多數情況下,共享的公共行為將包含在一個抽象類中,其他所有類從該類繼承。但是,我們不能使 Web 服務對象繼承功能。借此良機,通過這個問題說明 AOP 功能如何強大。

我們的應用程序用于顯示聯系人信息。最初它的用途是顯示信息,但是現在需要添加某些行為。為了查看代碼示例,我們需要創建一個稱為 TheAgileDeveloper.ContactService 的虛擬目錄。該目錄必須指向 TheAgileDeveloper.ContactService 項目在本地計算機上的位置。

通過 http://localhost/TheAgileDeveloper.ContactService 可以訪問此項目,這一點很重要。


1. 應用程序屏幕快照。

應用程序有一個視圖,它是一個名為 MainForm 的 WinForm,用于顯示左側 ListView 中 Web 服務返回的聯系人對象。選定一個聯系人時,名字、姓氏和 Web 頁將顯示在右側的文本框中。載入 MainForm 時,它調用 ServiceManager 類來獲取聯系人信息。下列 ServiceManager 類乍看起來似乎沒有添加任何值,只不過在窗體和 Web 服務之間添加了另一層。但是,它的價值就在于提供了一個在 Web 服務中添加新功能的位置,而不用重復代碼。另一個優點是,它將 Web 服務的“足跡”抽象出來,并從整個應用程序中移除出去。

Public Class ServiceManager

    Public Shared Function GetAllContacts() As ContactService.Contact()
        Dim service As ContactService.Service = New ContactService.Service
        Dim contacts() As ContactService.Contact = service.GetAllContacts
        Return contacts
    End Function

    Public Shared Sub SaveContact(ByVal contact As ContactService.Contact)
        Dim service As ContactService.Service = New ContactService.Service
        service.SaveContact(contact)
    End Sub

End Class

請查看 TheAgileDeveloper.Client 項目中的 Reference.vb 文件。它是在導入 ContactService 的 Web 引用時通過 wsdl.exe 創建的。它從 WSDL 自動生成以下 Contact 類。

'<remarks/>
    <System.Xml.Serialization.XmlTypeAttribute(_  
[Namespace]:=http://tempuri.org/TheAgileDeveloper.ContactService/Service1 _ )>  _
    Public Class Contact
        
        '<remarks/>
        Public Id As Integer
        
        '<remarks/>
        Public FirstName As String
        
        '<remarks/>
        Public LastName As String
        
        '<remarks/>
        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),因為應用混入后,結合 ContactContactSave 的新 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 時都會恢復為上一次接收更改的狀態。如果該接口是在一個部分類中實現的,那么在每個希望包含該行為的類中,都必須不厭其煩地重復實現這三個方法。作為一個實用主義編程人員,我嘗試堅持“一次且僅一次代碼”原則,所以我永遠不想重復任何代碼,復制和粘貼越少越好。通過使用混入,我能夠重用實現 IUndoableFieldUndoer 對象。在 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
encaser.AddPointcut("Save", "AcceptChanges") 

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.NET — 流行的 Java Spring 框架的一個 .NET 版本。在下一個版本中將實現 AOP。

?

Eos — 用于 C# 的一個面向方面的擴展。

小結

本文的目的是說明一種比常規日志記錄或安全實例更實用的應用 AOP 的新方法。正確應用使用 AOP 會帶來很多優點,甚至能夠幫助您完成常規編程選項所不能完成的成果任務。我強烈推薦您在 internet 上搜尋大量可用資源,以指導應用 AOP 的方法和場景時機。

關于作者

Matthew Deiters 對于軟件開發工作充滿熱情,他是 ThoughtWorks 的一名咨詢人員。他曾協助通過 .NET Framework 開發一些針對金融和保險行業的企業級系統。他看重 XP 編程和 TTD 方法論,認為大多數人為問題能夠通過設計模式和/或良好的單元測試解決。您可以通過 Matthew 的個人 Web 空間與他聯系:www.theAgileDeveloper.com

轉到原英文頁面


posted on 2006-01-10 08:42 夢在天涯 閱讀(472) 評論(0)  編輯 收藏 引用


只有注冊用戶登錄后才能發表評論。
網站導航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


公告

EMail:itech001#126.com

導航

統計

  • 隨筆 - 461
  • 文章 - 4
  • 評論 - 746
  • 引用 - 0

常用鏈接

隨筆分類

隨筆檔案

收藏夾

Blogs

c#(csharp)

C++(cpp)

Enlish

Forums(bbs)

My self

Often go

Useful Webs

Xml/Uml/html

搜索

  •  

積分與排名

  • 積分 - 1814985
  • 排名 - 5

最新評論

閱讀排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
      <noscript id="pjuwb"></noscript>
            <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
              <dd id="pjuwb"></dd>
              <abbr id="pjuwb"></abbr>
              欧美视频久久| 国产欧美日韩伦理| 亚洲激情校园春色| 久久精品九九| 久久婷婷激情| 你懂的网址国产 欧美| 久久美女艺术照精彩视频福利播放| 亚洲欧美一区二区原创| 午夜在线观看免费一区| 午夜在线电影亚洲一区| 久久久亚洲影院你懂的| 欧美高清视频一区| 亚洲人成网站在线观看播放| 亚洲高清不卡在线观看| 一区二区电影免费在线观看| 亚洲视频一区二区免费在线观看| 亚洲性感激情| 久热成人在线视频| 欧美日韩亚洲高清| 国产欧美1区2区3区| 伊人久久综合| 在线一区二区三区四区五区| 午夜精品久久久久影视 | 夜夜嗨av一区二区三区四季av| 亚洲视频专区在线| 久久先锋影音av| 欧美日韩精品| 一区二区三区 在线观看视| 亚洲婷婷综合色高清在线| 欧美在线播放| 欧美日韩国产成人精品| 国内成人自拍视频| 亚洲色在线视频| 久久综合久久久久88| 99精品国产在热久久婷婷| 久久免费视频在线观看| 国产精品美女久久久久av超清 | 一区在线影院| 亚洲综合视频1区| 欧美激情一区二区三区蜜桃视频| 一区二区三区视频在线| 久久综合久久综合这里只有精品| 欧美视频一区二区三区| 亚洲国产日韩在线一区模特| 欧美综合国产| 亚洲午夜在线视频| 欧美日韩精品免费在线观看视频| 影音先锋中文字幕一区二区| 欧美一区二区免费观在线| 亚洲精品资源| 欧美α欧美αv大片| 在线观看日韩国产| 久久久精品动漫| 亚洲综合日韩在线| 国产精品日韩精品| 亚洲校园激情| 日韩一级片网址| 欧美久色视频| 在线一区二区三区四区| 91久久精品一区二区别| 媚黑女一区二区| 亚洲精品1区2区| 欧美国产高潮xxxx1819| 免费欧美日韩| 亚洲精品护士| 最新国产成人在线观看| 欧美精品在线一区| 99视频精品免费观看| 亚洲黄色免费电影| 欧美成人精品在线播放| 亚洲精品在线视频观看| 亚洲日韩视频| 国产精品v亚洲精品v日韩精品| 亚洲午夜电影| 亚洲一区二区三区中文字幕| 国产精品日韩专区| 久久久久久久久久久久久9999| 午夜激情综合网| 一区二区自拍| 亚洲三级免费电影| 欧美亚州韩日在线看免费版国语版| 这里只有精品丝袜| 一区二区电影免费观看| 国产欧美1区2区3区| 免费一级欧美片在线观看| 国产亚洲综合性久久久影院| 18成人免费观看视频| 欧美成人精品一区| 欧美久色视频| 欧美在线免费播放| 久久综合九色| 亚洲免费在线视频| 久久精品国产欧美激情| 亚洲国产精品一区二区三区| 亚洲欧洲在线看| 国产婷婷色一区二区三区| 女女同性女同一区二区三区91| 欧美激情视频一区二区三区在线播放| 在线亚洲精品福利网址导航| 亚洲自拍高清| 99精品热6080yy久久| 亚洲免费视频一区二区| 1024亚洲| 欧美一级视频免费在线观看| 亚洲精品社区| 香蕉成人久久| 99在线|亚洲一区二区| 欧美一区2区视频在线观看| 亚洲精品欧美专区| 久久高清免费观看| 亚洲欧美区自拍先锋| 欧美激情第六页| 久久婷婷色综合| 国产精品美女在线| 亚洲欧洲精品一区二区| 国内免费精品永久在线视频| 亚洲美女精品成人在线视频| 亚洲国产经典视频| 欧美有码在线视频| 亚洲欧美激情视频| 欧美日韩国产首页在线观看| 欧美高清视频一区| 在线不卡中文字幕| 久久精品国产v日韩v亚洲 | 国产午夜精品久久久| 99国产成+人+综合+亚洲欧美| 亚洲国产精品国自产拍av秋霞| 亚洲一区二区三区精品视频| aa日韩免费精品视频一| 免费成人毛片| 欧美国产综合视频| 伊人久久亚洲美女图片| 欧美伊人久久久久久久久影院 | 国语自产精品视频在线看一大j8| 这里只有视频精品| 中文国产亚洲喷潮| 欧美日韩视频免费播放| 亚洲精品国产精品久久清纯直播 | 欧美大片免费久久精品三p| 老巨人导航500精品| 韩国在线视频一区| 99国产精品久久久| 性欧美xxxx大乳国产app| 夜夜嗨av一区二区三区网页| 欧美激情一区二区三级高清视频| 欧美大片在线观看一区| 亚洲黄色视屏| 欧美精品自拍偷拍动漫精品| 亚洲理论在线观看| 亚洲在线成人| 国产啪精品视频| 久久久久**毛片大全| 欧美freesex8一10精品| 亚洲欧洲精品成人久久奇米网| 免费国产自线拍一欧美视频| 亚洲国产一区二区三区高清 | 国产精品久久久久99| 亚洲女性裸体视频| 久久久久99精品国产片| 亚洲高清一区二| 欧美激情精品久久久久久变态| 亚洲三级视频| 欧美淫片网站| 亚洲欧洲视频在线| 欧美午夜免费| 久久国产一二区| 亚洲日本一区二区三区| 亚欧成人在线| 亚洲品质自拍| 国产精品日韩欧美| 麻豆精品在线视频| 这里只有精品在线播放| 嫩草成人www欧美| 亚洲午夜av电影| 亚洲风情亚aⅴ在线发布| 欧美日韩美女在线观看| 欧美亚洲免费高清在线观看| 亚洲第一网站免费视频| 午夜精品美女自拍福到在线| 在线精品亚洲| 国产精品你懂的在线| 免费精品视频| 午夜久久影院| 99re这里只有精品6| 蜜桃av久久久亚洲精品| 亚洲欧美日韩在线播放| 亚洲精品麻豆| 在线播放日韩欧美| 国产精品一区二区在线观看| 久久久久久久综合| 亚洲欧美不卡| 日韩午夜电影av| 欧美国产日韩在线| 欧美在线免费观看| 中文欧美日韩| 亚洲精品视频啊美女在线直播| 国内精品视频久久| 国产精品日日做人人爱| 欧美日韩免费看| 麻豆国产va免费精品高清在线|