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

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

搜索

  •  

積分與排名

  • 積分 - 1815007
  • 排名 - 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>
              亚洲福利视频网站| 99热精品在线观看| 日韩亚洲视频| 亚洲欧洲日本mm| 99亚洲视频| 亚洲欧美成aⅴ人在线观看| 在线亚洲欧美视频| 亚洲永久免费视频| 欧美在线观看网址综合| 久久九九全国免费精品观看| 久久网站热最新地址| 欧美成人有码| 一本色道久久88亚洲综合88| 午夜精品免费| 看欧美日韩国产| 欧美日韩一区二区三| 国产欧美日韩综合一区在线观看| 国产综合视频在线观看| 亚洲免费av片| 午夜亚洲福利| 免费亚洲一区| 一区二区三区蜜桃网| 久久久xxx| 欧美日韩国产高清视频| 国产精品日韩在线播放| 在线免费不卡视频| 午夜精彩视频在线观看不卡 | 国产精品久久久爽爽爽麻豆色哟哟| 国产精品第一区| 在线观看亚洲| 亚洲欧美综合另类中字| 欧美国产精品劲爆| 香港成人在线视频| 欧美另类99xxxxx| 黑丝一区二区| 亚洲综合日韩| 亚洲欧洲精品一区| 欧美一区二区三区免费观看| 欧美日韩国产色站一区二区三区| 激情丁香综合| 欧美一区亚洲| 国产精品亚洲一区二区三区在线| 亚洲男人的天堂在线观看| 久久精品国产精品亚洲综合| 欧美精品在线一区二区| 久久久国产视频91| 欧美日韩成人一区二区三区| 亚洲电影有码| 久热国产精品| 久久久99爱| 黄色成人免费观看| 久久久久天天天天| 欧美一区二区三区四区夜夜大片| 欧美无乱码久久久免费午夜一区 | 久久―日本道色综合久久| 国产精品99久久久久久有的能看| 欧美黑人一区二区三区| 亚洲国产日韩一区| 欧美a级大片| 久久精品日韩欧美| 影音先锋日韩精品| 六月婷婷一区| 久久久久国产免费免费| 在线精品在线| 欧美激情一区二区三区不卡| 免费在线欧美视频| 亚洲精品一区二区三区四区高清 | 亚洲国产欧美日韩精品| 蜜臀a∨国产成人精品| 亚洲国产高清在线观看视频| 欧美成人久久| 欧美不卡在线视频| 一区二区三区色| 99在线观看免费视频精品观看| 欧美三级日本三级少妇99| 亚洲欧美日韩精品在线| 欧美淫片网站| 亚洲国产精品美女| 亚洲人成网站在线播| 国产精品www色诱视频| 久久精品国产精品亚洲精品| 老司机精品导航| 亚洲一区二区三区在线看| 午夜一区二区三区在线观看| 在线不卡中文字幕| 亚洲国产综合在线| 国产精品乱码一区二三区小蝌蚪| 久久精品女人的天堂av| 久热re这里精品视频在线6| 亚洲免费高清| 午夜宅男久久久| aⅴ色国产欧美| 亚洲欧美日韩精品久久| 亚洲激情综合| 亚洲欧美视频一区| 亚洲欧洲日本国产| 亚洲女人天堂成人av在线| 国产亚洲精品bt天堂精选| 一区在线免费| 亚洲电影免费观看高清完整版在线 | 亚洲精品影视在线观看| 国产精品人人做人人爽| 美女诱惑一区| 欧美三日本三级少妇三99| 巨乳诱惑日韩免费av| 国产精品久久久久国产a级| 欧美不卡视频一区发布| 国产精品国产三级国产a| 国产精品久久久久aaaa九色| 裸体素人女欧美日韩| 亚洲一区免费观看| 亚洲国产日本| 一本色道久久99精品综合| 国产网站欧美日韩免费精品在线观看| 噜噜噜躁狠狠躁狠狠精品视频| 国产精品xxxxx| 欧美一区二区在线| 欧美看片网站| 欧美黄色影院| 黑丝一区二区三区| 欧美亚洲一区二区在线| 亚洲在线视频| 欧美精品尤物在线| 免费精品99久久国产综合精品| 国产精品亚洲а∨天堂免在线| 亚洲精品在线观看免费| 亚洲国产精品小视频| 久久国产加勒比精品无码| 亚洲午夜激情网站| 欧美精品三级在线观看| 欧美黑人在线播放| 亚洲电影免费在线观看| 巨乳诱惑日韩免费av| 欧美国产视频在线观看| 亚洲福利视频网| 欧美91精品| 亚洲福利一区| 亚洲视频在线观看免费| 欧美绝品在线观看成人午夜影视| 亚洲激情在线观看| 亚洲免费av网站| 欧美午夜欧美| 一区二区高清| 亚洲综合精品| 国产综合亚洲精品一区二| 久久久噜噜噜久久久| 另类av导航| 亚洲免费视频网站| 国产综合久久久久久鬼色| 亚洲国产欧美不卡在线观看| 日韩天堂在线视频| 欧美日韩精品不卡| 亚洲一区二区免费看| 欧美在线视频二区| 影音先锋中文字幕一区| 欧美成人一二三| 亚洲无线视频| 亚洲国产高潮在线观看| 久久亚洲精品视频| 亚洲激情成人| 国产夜色精品一区二区av| 欧美资源在线观看| 欧美成人激情视频| 夜夜精品视频| 国产毛片久久| 久久免费国产| 日韩视频中文| 久久精品一二三| 91久久精品美女| 国产精品免费一区二区三区观看| 亚洲欧美日韩在线观看a三区| 久久综合激情| 在线国产精品一区| 欧美午夜不卡影院在线观看完整版免费 | 国产精品视频免费在线观看| 午夜精品免费| 亚洲片区在线| 久久综合色8888| 夜夜嗨av一区二区三区网页| 国产欧美日韩视频在线观看 | 在线日韩av| 欧美系列精品| 久热精品在线视频| 亚洲欧美清纯在线制服| 久久久久国产精品麻豆ai换脸| 亚洲国产视频直播| 国产精品视频导航| 欧美激情一区二区三区全黄| 99在线视频精品| 久久一二三区| 午夜一区二区三区在线观看| 亚洲国产精品毛片| 国产在线国偷精品产拍免费yy| 蜜月aⅴ免费一区二区三区| 亚洲免费人成在线视频观看| 亚洲欧洲在线视频| 久久久999精品| 亚洲一区3d动漫同人无遮挡| 日韩一级免费观看| 亚洲欧洲日韩综合二区|