




已閱讀5頁,還剩135頁未讀, 繼續(xù)免費閱讀
版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
用AspectJ增強設計模式上設計模式長期以來一直是一些經驗豐富的開發(fā)人員的工具箱的重要組成部分。不幸的是,因為模式可以影響多個類,所以它們也是侵入性的、難于使用(和重用)。本文是 AOPWork 系列的第 3 部分,是一篇由兩部分組成的文章,在這篇文章中,Nicholas Lesiecki 將介紹 AOP 是怎樣通過根本轉變模式實現(xiàn)來解決這一問題的。他研究了三個經典的 Gof 設計模式 (適配器模式、修飾器模式和觀察者模式),同時還將討論使用面向方面技術實現(xiàn)這些模式所帶來的實踐和設計方面的好處。 什么是設計模式?根據(jù) Design Patterns: Elements of Reusable Object-Oriented Software: 設計模式系統(tǒng)地命名、促進和解釋了解決面向對象系統(tǒng)中重復出現(xiàn)的設計問題的一個通用設計。它描述了問題、解決方案、何時應用該解決方案以及所產生的結果。它還提供了一些實現(xiàn)提示和示例。解決方案是解決問題的對象和類的總體安排??梢远ㄖ撇崿F(xiàn)解決方案,解決具體上下文環(huán)境中的問題。 在多年成功地應用模式解決 OO 系統(tǒng)中的問題之后,我發(fā)現(xiàn)自己也認同了這個定義。模式是與普通程序員談論設計的最好方式,它們代表解決“重復出現(xiàn)的設計問題”的最佳實踐。所以,當我參加了 Stuart Halloway 的一次訪談時,我感到有點震驚:他為 GoF 提供了另一個頭銜:“處理 C+ 中破損事物的修理廠”。他的觀點是:在一種語言中以“模式”方式存在的東西,在不同的范式下,可以融入語言本身。接著他給出了 Factories 的示例 該示例在 Java 語言中有用,但在 Objective-C 中卻沒多大用,后者支持從構造函數(shù)中返回子類型。 我思考了很長一段時間,然后我認識到兩個方面實際上說的是同一件事之前:設計模式提供了表達那些無法直接在編程語言中表達的概念的詞匯表。 那么,AOP 位居何處呢?對于 OOP,我們有 GoF 模式,它提供了處理公共的概念(像觀察者和修飾器)的統(tǒng)一方法(盡管有時有點麻煩)。AOP 構建在 OOP 之上,提供了表達橫切關注點的直接方式。它認為某些 GoF 模式是關于橫切的,可以直接用 AOP 表示。所以您會注意到,對于一些包含許多類的模式,用一個方面就可以表達。有些模式變得更易使用,因為它們可以包含更少代碼。有些模式得到了非常好的支持,以致于它們幾乎消失不見。其他模式嚴格綁定到 OOP (例如處理類結構的模式),所以它們在與 AOP 結合使用的時候保持不動。 本文的目的是探索用 AOP (特別是用 AspectJ)進行的模式實現(xiàn)。我選擇 GoF 模式,是因為它是一個非常流行和通用的工具。在本文的第 1 部分中,我要設置一些分析模式影響的指標,然后研究適配器和修飾器模式。適配器會演示靜態(tài)交叉的優(yōu)勢,而修飾器則會暴露它自身是一個正在消失的模式。在 第 2 部分中,我將提供對觀察者模式更加深入的研究,這種模式并沒有消失,但您會看到在用 AspectJ 實現(xiàn)它時的一些主要好處。第 2 部分將顯示 AspectJ 如何使模式轉變成可重用的基本方面,從而允許您下載預先構建好的模式庫 這是讓模式愛好者們興奮的一大優(yōu)勢。 1為什么把 AOP 應用到設計模式? 我前面說過,許多模式都是橫切的,當然我不是第一個想到這一點的人。最近有一篇研究論文對 GoF 模式進行了分析,分析發(fā)現(xiàn):在 23 個模式中,有 17 個模式表現(xiàn)出某種程度的橫切。(這篇論文是 Jan Hannemann 和 Gregor Kiczales 合著的“Java AspectJ 中的設計模式實現(xiàn)”,請參閱 參考資料 一節(jié),以獲取更多細節(jié)。) 如果 AOP 承諾可以協(xié)助解決橫切,那么在設計模式上使用 AOP 有什么好處呢?我先從用通用術語回答這個問題開始,然后設置一個框架,通過它來考察每個設計模式。 在設計模式上使用 AOP 的好處 AOP 第一個關鍵的好處是把指定設計模式的代碼 本地化 的能力。這意味著通常只在一個方面或一對密切關聯(lián)的方面上就可以實現(xiàn)模式。(這與 Java 語言實現(xiàn)形成對比,在 Java 語言實現(xiàn)中,模式應用程序可以分布在多個類中。)能夠在一個地方看到所有代碼會帶來幾個實際的好處。首先,如果模式的所有交互都能在一個地方看到的話,那么閱讀代碼的人就能更容易地理解模式。其次,如果開發(fā)人員需要改變模式的實現(xiàn),那么他或她就能在一個地方修改模式,而不用在整個系統(tǒng)中追蹤模式的片斷。最后,開發(fā)人員可以用有意義的名稱描述封裝模式的方面,為以后的維護人員提供有關模式意圖的文字線索。例如,可以把方面命名為 SensorAdapter,這表明在傳感器上使用的是適配器模式。 AspectJ 模式實現(xiàn)的另一個關鍵好處就是 遺忘性(obliviousness)。換句話說,在模式中發(fā)揮作用的類不必知道這個角色。這個好處直接來自本地化 因為模式是在某一個方面實現(xiàn)本地化的,所以不需要冒犯其參與者。遺忘性讓模式參與者更容易理解代碼。不僅如此,遺忘性還讓模式更容易組合。在 Java 語言實現(xiàn)中,如果類參與到多個模式中,模式的機制會迅速模糊它的核心含義。如果類不知道那些在模式中的參與者,那么可以在其他上下文中重用這些類。在本文的 第 2 部分 中將看到這方面的一個具體示例。 這些好處允許對某些模式實現(xiàn)代碼級重用。設計模式的概念和結構一直都是可重用的。如果想實現(xiàn)觀察者模式,任何人都可以找出 GoF 的書籍,把模式應用到自己的代碼中。但是,如果使用面向方面技術,那么通過下載 ObserverProtocol 方面 (可以在設計模式計劃中獲得它,請參閱參考資料 一節(jié)),就可以避免這個麻煩。除了節(jié)省的實現(xiàn)工作之外,代碼級重用還允許模式代碼和文檔進行更緊密的耦合。例如,我可以瀏覽 ObserverProtocol 的 javadoc,不用另找一本書就可以理解它的意圖和結構。 分析框架 每個模式的描述都跟著一個公共結構。我將從一個示例問題開始介紹,提供對這個模式的通用描述。然后我會描述如何實現(xiàn)這個模式,先使用 Java 語言實現(xiàn)它,然后使用 AspectJ 語言實現(xiàn)它。在每個實現(xiàn)后面,我都會描述是什么造成了模式的橫切,以及這個版本的模式在理解、維護、重用和編排代碼的時候有什么效果。1適配器模式 我要詳細考慮的第一個模式就是適配器模式。適配器模式完全是關于兼容性的。這個模式允許類與其他原來由于接口不兼容而無法交互的類進行交互。要在 Java 代碼中實現(xiàn)適配器,需要用特殊的適配器類包裝目標類,適配器類能把目標類的 API 轉換成客戶想要的 API,或者轉換成能夠更容易利用的 API。 設置:提供一個聚合的傳感器讀取器 假設現(xiàn)在要設計一個航天器,則需要提供一個讀取器,讀取航天器上所有關鍵傳感器。由于是在擴展現(xiàn)有系統(tǒng),所以對于每個要訪問的傳感器而言,都存在方便的 Java 類。例如,可以用以下類訪問溫度計: public class TemperatureGaugepublic int readTemperature()/accesses sensor internals 可以用以下類訪問輻射傳感器:public class RadiationDetector public double getCurrentRadiationLevel()/read radiation somehow 這些傳感器類中有一些是其他團隊成員編寫的,有一些是第三方供應商編寫的?,F(xiàn)在想要做的是提供一個顯示方式,顯示每個傳感器的狀態(tài),這樣司令官只要看一眼,就知道飛船是否有問題。以下是所需要功能的一個示例。(實際的顯示可能包含閃爍的紅燈和高音喇叭,但現(xiàn)在我們只顯示文本。) Readout: Sensor 1 status is OK Sensor 2 status is OK Sensor 3 status is BORDERLINE Sensor 4 status is DANGER! 可以用以下方法實現(xiàn)上述顯示: public static void main(String args)RadiationDetector radiationDetector = /find critical detector TemperatureGauge gauge = /get exhaust nozzle gauge List allSensors = new ArrayList();allSensors.add(radiationDetector);allSensors.add(gauge);int count = 1;for (Sensor sensor : allSensors) /How to read each type of sensor.? 目前為止,一切順利。但是怎樣才能不使用丑陋的 if(sensor instanceof XXX) 檢測就能讀出每個傳感器呢?選項將修改每個傳感器類,讓它們擁有 getStatus() 方法,以便解釋傳感器的讀取操作,并返回 String,如下所示。 if(this.readTemperature() 160)return DANGER;return OK 這樣做會將一些不相關的狀態(tài)顯示代碼帶到多個類當中,增加它們的復雜性。而且,可能存在一些實際限制 (例如必須重新編譯第三方類)。這就是適配器模式發(fā)揮作用的地方。 1Java 語言的適配器 適配器模式的傳統(tǒng)實現(xiàn)方式是:用一個實現(xiàn)了方便的 API 的類來包裝每個目標。在這種情況下,要創(chuàng)建一個公共接口,比如 StatusSensor,如下所示: public interface StatusSensorString getStatus(); 有這個公共接口存在,就可以像以下這樣實現(xiàn)讀取器方法: for (StatusSensor sensor : allSensors) System.out.print(Sensor + count+);System.out.println( status is + sensor.getStatus(); 剩下的惟一挑戰(zhàn)就是讓每個傳感器符合這個接口。適配器類可以實現(xiàn)這一點。在清單 1 中可以看到,每個適配器都以成員變量的形式保存自己包裝的傳感器,用這個底層的傳感器實現(xiàn) getStatus 方法: 清單 1. 適配器類和客戶代碼 /Adapter classes public class RadiationAdapter implements StatusSensor private final RadiationDetector underlying;public RadiationAdapter(RadiationDetector radiationDetector) this.underlying = radiationDetector;public String getStatus() if(underlying.getCurrentRadiationLevel() 1.5)return DANGER;return OK;public class TemperatureAdapter implements StatusSensor /.similar /glue code to wrap each sensor with its adapter. allSensors.add(new RadiationAdapter(radiationDetector);allSensors.add(new TemperatureAdapter(gauge); 清單還顯示了在讀取器之前用適當?shù)倪m配器包裝每個傳感器的“膠水代碼”。模式并沒有指定這個膠水代碼應當在哪個具體位置出現(xiàn)??赡艿奈恢冒ā皠?chuàng)建之后”和“使用之前”。可以將示例代碼放在向讀取器集合添加傳感器之前。1對 Java 語言適配器的分析 適配器模式在傳統(tǒng)的實現(xiàn)中運用得很成功。但是什么讓它產生橫切呢?是“狀態(tài)”關注點橫切了多個不同的傳感器類。如果想?yún)f(xié)同定位一個包中的適配器類,那么這個模式的 OO 實現(xiàn)會很好地得到模塊化。包會成為“適配器模塊”。包裝術語將阻止傳感器了解模式,從而形成更加松散的耦合。不幸的是,實現(xiàn)包裝任務的那部分應用程序既需要知道適配器,還需要知道應用適配器的傳感器。因此,模式會造成“膠水代碼”的位置成為橫切。 現(xiàn)在,讓我們來看看 Java 語言適配器是如何在我的評價指標上堆疊起來的: 易于理解:在包中協(xié)同定位的統(tǒng)一命名的 SensorAdapter,使得這個模式的意圖清晰。不幸的是,膠水代碼的位置可能遠在適配器之外。由于膠水代碼的區(qū)域不是結構化的,所以在試圖理解模式的時候會忽略它,或者在試圖理解它處理的代碼時忽略它。 您還必須關注對象標識的處理問題。也就是說,如果同一對象包裝和未包裝的版本同處在一個系統(tǒng)中,那么必須想好是否應當對它們同等對待。 重用:要重用這個模式,就必須從頭開始重新實現(xiàn)該模式。 維護:在向讀取器添加新傳感器時,必須添加新的適配器類,并更新包裝它們的膠水代碼。 組合:假設想要在另一個模式中包含傳感器。由于適配器已經忘記傳感器,所以它們不受影響。但這是把雙刃劍。新的模式應當把適配版本的傳感器當作自己的對象,還是應該將未適配版本的傳感器當作自己的對象呢? AspectJ 適配器 像使用其他設計模式一樣,適配器的 AspectJ 實現(xiàn)保留了它的同類的意圖和概念。這個實現(xiàn)采用類型間聲明,這是一個重要的橫切類型支持,要比切入點和通知(advice)的啟動時間更少。如果需要對靜態(tài)橫切進行回顧,請參閱 參考資料 一節(jié),以獲得適當?shù)闹甘尽?像使用純 OOP 版本一樣,AOP 版本的適配器需要 StatusSensor 接口。但是,AspectJ 版本沒有采用獨立的包裝器類,而是采用 聲明父母 的形式,讓不同的傳感器直接實現(xiàn) StatusSensor,如下如示: public aspect SensorAdapter declare parents :(TemperatureGauge | RadiationDetector)implements StatusSensor; 現(xiàn)在傳感器應當符合接口。但是它們還沒有 實現(xiàn) 接口 (這是 AspectJ 編譯器會很高興指出的一個事實)。要完成模式的實現(xiàn),必須向方面添加要使每個傳感器符合要求的類型間方法聲明。下面的代碼把 getStatus() 方法添加到 TemperatureGauge 類中: public String TemperatureGauge.getStatus()if(this.readTemperature() 160)return DANGER;return OK; AspectJ 版本的讀取器類看起來與用 Java 語言實現(xiàn)的版本相同,除了不必用膠水代碼包裝傳感器。每個傳感器同時又是自己的包裝器。 AspectJ 適配器的分析 用 AspectJ 實現(xiàn)適配器的關鍵好處是本地化。整個模式都包含在一個方面中,而不是兩個以上的獨立適配器和非結構化的“包裝”位置。這里是根據(jù)我的指標考察的 AspectJ 實現(xiàn)的情況: 易于理解:沒有包裝,理解模式只需查看適配器方面即可,這消除了四處查看的需要。沒有包裝還消除了處理對象標識問題的需要。 重用:AspectJ 版本與其他版本具有同樣的可重用性。 維護:因為每個新傳感器只包括編寫一個方法(而不是完整的類),所以擴展 AspectJ 實現(xiàn)應當略微容易些。隨著適配器的數(shù)量增長或者每個適配要求的邏輯變復雜,可能會發(fā)現(xiàn)單一的方面會變得不合理地長。在這種情況下,可以把一個方面拆分成幾個方面。拆分方面會損失本地化的好處,但是可以保留其他好處。 組合:可以很容易地組合多個模式,因為不需要處理包裝協(xié)調的問題。 結果是:Java 和 AspectJ 實現(xiàn)在處理傳感器類的方式上都做得不錯。但是,只有 AspectJ 版本處理了應用程序的其他方面。這是一個主要優(yōu)勢嗎?這可能取決于應用程序是否會表現(xiàn)出分析中描述的復雜屬性。如果我正對某一個項目使用 AspectJ,那么我肯定會用它實現(xiàn)適配器,雖然我介紹 AspectJ 不僅僅是為了解決這個問題。下一個模式,即修飾器模式,提供了一些更引人注目的優(yōu)勢。修飾器模式 從面向方面的角度來考慮,修飾器是一個有趣的模式,因為它是接近“消失”的模式之一,具有面向方面的語言(例如 AspectJ)所引入的能力。為什么這么說呢?如果深入研究修飾器在面向方面和面同對象實現(xiàn)中的演變,就會讓事情變得更清晰。 修飾器模式的目標是動態(tài)地把功能添加到現(xiàn)有對象上。GoF 書中給出的規(guī)范示例包括文字修飾。在他們的示例中,GUI 組件類是在一個修飾器類中包裝的,可以添加邊框或者是滾動條來顯示該組件。Java 類庫明顯非常支持修飾器,允許用java.util.Collections 的方法修飾 Collection,這樣,集合就變成不可修改的或者是同步的,還有各種各樣的 IO 流,它們可以緩沖、擴大或者監(jiān)視其他流。 設置:監(jiān)視文件讀取 為了給這個示例增加一些趣味,我選擇了 Java 發(fā)行包中的修飾器,用它來查看用 AspectJ 復制一個修飾器時需要做些什么。其中一個我覺得有趣的修飾器是來自 javax.swing 的 ProgressMonitorInputStream。根據(jù)記錄,ProgressMonitorInputStream 監(jiān)視底層輸入流的讀取進度。 為了演示修飾的實際效果,我編寫了一個簡單的讀取文件的 GUI。可以在清單 2 中查看讀取輸入流的代碼,還可以在圖 1 看到運行的應用程序的截屏。(也可以單擊這一頁頂部或底部的 代碼 圖標,研究示例的完整源代碼。) 圖 1. 流的 ProgressMonitor 在考慮下面一節(jié)的時候,您可能想擁有 java.io 和 ProgressMonitorInputStream javadoc 或者源代碼,請參閱 參考資料,以獲得更多參考消息。1Java 語言的修飾器 在 Java 語言中,最初是通過創(chuàng)建 AbstractComponent 接口 (或類)認識修飾器模式的,基本實現(xiàn) (有時稱為 ConcreteComponent) 和修飾器都要符合這一點。在這個示例中,AbstractComponent 是 java.io.InputStream,它定義了 FileInputStream (ConcreteComponent) 和 BufferedInputStream (ConcreteDecorator) 的接口。 雖然并不是嚴格要求的,但修飾器實現(xiàn)通常提供一個 AbstractDecorator,它維護一個對已修飾組件的引用,并在不添加額外行為的情況下提供基本的修飾機制。在 java.io 中,F(xiàn)ilterInputStream 提供了這項功能。最后,ConcreteDecorator 擴展了 AbstractDecorator,覆蓋了需要修飾的方法,并在調用已修飾組件上的相同方法之前或之后添加行為。在這種情況下,ProgressMonitorInputStream 使用了 ConcreteDecorator。 看一看 Sun 的 ProgressMonitorInputStream 實現(xiàn)(由于許可考慮,我在這里不再重新打?。?,可以看到它在創(chuàng)建 javax.swing.ProgressMonitor 時實例化了該監(jiān)視器。在每個 read 方法后面,它用從底層流中讀取的字節(jié)數(shù)更新監(jiān)視器。ProgressMonitor 類決定了什么時候彈出監(jiān)視對話框并更新可視顯示。 要使用 ProgressMonitorInputStream,只需要包裝另外一個輸入流(如清單 2 所示),并確保在進行讀取操作時引用已包裝的實例即可。在這里,請注意適配器和修飾器模式之間的相似性:兩者都需要以編程方式對目標類應用額外的行為。 清單 2. 監(jiān)視 InputStream private void actuallyReadFile() try InputStream in = createInputStream();byte b =new byte1000;while (in.read(b) != -1) /do whatever here bytesRead+=1000;bytesReadLabel.setText(Read + (bytesRead/1000) + k);bytesRead = 0;in.close(); catch (Exception e) /handle. private InputStream createInputStream() throws FileNotFoundExceptionInputStream stream = new FileInputStream(name.getText();stream = new BufferedInputStream(stream);/_this_ is a JPanel GUI component stream = new ProgressMonitorInputStream(this, This is gonna take a while, stream);return stream; Java 語言修飾器的分析 查看以上示例之后,看起來沒有東西比使用修飾器模式更簡單了。但是,不要忘記,為了使這個示例能夠運行,Sun 實現(xiàn)了InputStream、FilterInputStream 和 ProgressMonitorInputStream 代碼的數(shù)量可不是微不足道的。 在這個示例中,監(jiān)視的關注點橫切了 InputStream。更加通用的情況是,修飾可以橫切修飾的目標。更進一步地說,修飾關注點可以橫切應用程序。例如,用戶可能要求一個針對 全部 文件輸入的 ProgressMonitor。(為了避免您把它當作一個人為的示例,請自己問一下自己,有多少次您使用輸入流的時候,沒有 對其進行緩沖。) 現(xiàn)在看一下剩下的指標: 易于理解:一旦了解了修飾器的工作,就很容易理解它。但是我永遠不會忘記我在第一次打開 java.io 并試圖了解組成應用于流上的機器類(machinery class)的健康情況時所產生的迷惑。雖然一份快速培訓教程可以很容易地讓我擺正方向,但這只適用于查看代碼,沒有理解模式的簡易途徑。更具體地度量理解方面的負擔的方法是計算代碼行數(shù)。在研究完 AspectJ 實現(xiàn)之后,我將看一下行數(shù)。不過,還是一點價值都沒有,由于修飾器使用了包裝功能,所以它也會遭遇那些影響適配器的對象標識問題。 重用:要重用這個模式實現(xiàn),必須重新實現(xiàn)它。 維護:有兩個關鍵的維護場景:在第一個場景中,要向現(xiàn)有實現(xiàn)添加新的修飾器。根據(jù)修飾器中方法的數(shù)量,這項工作可能很冗長,但顯然不是很難。在第二個場景中,要向 AbstractComponent 添加新操作(即 InputStream)。這意味著要更新所有現(xiàn)有修飾器,以反映新的操作,還要針對每個修飾器作出決策,決定是不是應當把修飾應用到新方法上。 組合:因為修飾器和組件共享公共接口,所以修飾器允許在指定實例上透明地組合修飾器。(參見清單 2,在那里,代碼緩沖也將監(jiān)視輸入流)。這非常好,特別是因為修飾的目標不必知道自己受到了修飾。1AspectJ 修飾器 在 Hanneman 和 Kiczales 合著的論文中,他們談到了以下這點: 如果使用 AspectJ,那么某些模式的實現(xiàn)就會完全消失,因為 AspectJ 語言的構造直接實現(xiàn)了它們。這適用于 修飾器。 查看一下 Gof 書中關于使用修飾器的動機的那一節(jié),然后您就會很清楚為什么會是這種情況了: 修飾器把請求轉發(fā)到組件,在轉發(fā)前后可能執(zhí)行附加動作(例如繪制邊框)。透明性支持遞歸地嵌套修飾器,所以它支持無限數(shù)量的附加功能。 那么所給的建議是什么呢?是透明地把附加“操作”添加到任何操作上的能力?從某種意義上說,AspectJ 支持對任何方法進行修飾。為了看到它在真實系統(tǒng)中的作用,可以研究 AspectJ 實現(xiàn)的輸入流讀取監(jiān)視。 識別被修飾的操作 為了正確地實現(xiàn)監(jiān)視,方面必須識別出流上的讀取操作。通過編寫一個撿取讀取取操作的切入點可以做到這一點: pointcut arrayRead() :call(public int InputStream+.read(.); 現(xiàn)在可以應用一些下面這樣通用格式的通知: after() returning (int bytesRead) :arrayRead()updateMonitor(bytesRead); 此通知用 returning 格式公開方法調用的返回值。讀取的字節(jié)數(shù)被傳遞給方面上的一個私有方法:updateMonitor()。該方法負責更新實際的 ProgressMonitor 的細節(jié)(稍后會有更多關于這項工作的內容)。 公開相關上下文 到目前為止,解決方案都很簡單。但是,這表明 ProgressMonitor 類還要求做幾件事才能實現(xiàn)它的工作。具體地說,它需要一個 GUI 組件來綁定監(jiān)視對話模式。在傳統(tǒng)的實現(xiàn)中可以看到這個要求: /this is a JPanel GUI component stream = new ProgressMonitorInputStream(this, This is gonna take a while, stream); 要獲得需要的 GUI 組件,方面必須把它綁定到切入點,通知才能使用它。清單 3 包含修訂后的切入點和通知。注意,fromAComponent() 切入點利用了原始的 cflow() 切入點。切入點實際上是在說“選擇作為 JComponent 上面方法執(zhí)行結果的全部連接點,并公開這個組件,供通知使用?!?清單 3. 用 cflow 將上下文環(huán)境交給監(jiān)視器 pointcut arrayRead(JComponent component, InputStream is) :call(public int InputStream+.read(.) & target(is)& fromAComponent(component);pointcut fromAComponent(JComponent component) :cflow(execution(* javax.swing.JComponent+.*(.)& this(component);after(JComponent component, InputStream is) returning (int bytesRead) :arrayRead(component, is)updateMonitor(component, is, bytesRead); 維護狀態(tài) 為了讓方面的應用面更廣(也為了準確地模擬其他實現(xiàn)),方面必須維護狀態(tài)。也就是說,它應當為每個受監(jiān)視的流彈出一個惟一的進度監(jiān)視器。AspectJ 提供了幾個處理這個問題的選擇。對于這個方面來說,最佳選擇可能是用 Map 維護每個對象的狀態(tài)存儲。這種技術在我實現(xiàn)的觀察者模式中會再次出現(xiàn),請注意觀察?。▽⑻囟顟B(tài)保存到對象的其他方法中,包括類型間聲明和 pertarget/perthis 方面,但是對這些概念的考慮超出了本文的范疇。) 要實現(xiàn)狀態(tài)存儲,首先要聲明一個 WeakHashMap,它用流作為鍵,把監(jiān)視器保存為值??梢允褂?WeakHashMap,因為如果正常使用中不再需要鍵,那么 WeakHashMaps 不會阻止將它的鍵作為垃圾進行收集。這個最佳實踐可以防止由于方面持有對不活動對象的引用而造成的內存泄漏。 然后 updateMonitor() 方法用 map 消極地初始化一個新的 IncrementMonitor。一旦該方法確定存在監(jiān)視器,就會用最新的進度(read() 的返回值表示)來更新監(jiān)視器。在清單 4 中,可以看到實現(xiàn)消極初始化和進度更新的代碼,以及 IncrementMonitor 的完整代碼: 清單 4. 每個流監(jiān)視器的消極初始化 /From the aspect. private void updateMonitor(JComponent component, InputStream is,int amount)IncrementMonitor monitor =(IncrementMonitor)perStreamMonitor.get(is);if(monitor = null)monitor = initMonitor(is, component);monitor.increment(amount);private IncrementMonitor initMonitor(InputStream is,JComponent component)try int size = is.available();IncrementMonitor monitor =new IncrementMonitor(component, size);perStreamMonitor.put(is, monitor);return monitor; catch (Exception e) /.handle /.end aspect public class IncrementMonitor extends ProgressMonitorprivate int counter;public IncrementMonitor(Component component, int size)super(component, Some Title, null, 0, size);public void increment(int amount)counter += amount;setProgress(counter); 最后,在已經完全讀取完流的時候,方面需要釋放監(jiān)視器。如果在這個時候按照方面的思路去思考,您就會認識到這是個機會:InputStream 可以很方便地為將要通知的方面定義一個 close() 方法,如下所示: before(InputStream is):call(public void InputStream+.close()& target(is)System.out.println(Discarding monitor.);perStreamMonitor.remove(is); 目前,練習已經完成。但是,如果熟悉 InputStream 實現(xiàn)的話,那么可能會發(fā)現(xiàn),我故意遺漏了一些事。必須用與其他讀取方法不同的方法來處理 read() 方法 (沒有參數(shù)),因為它的返回值不是讀取的字節(jié)數(shù),而是讀取流中的下一個字節(jié)。隨著本文的示例代碼對方面進行擴展,可以處理這個限制,但是我建議您在參閱代碼之前,想想自己編寫方面時應當如何解決這個問題。AspectJ 修飾器的分析 像適配器模式一樣,修飾器模式的兩個實現(xiàn)的區(qū)別在于本地化。在 AspectJ 版本中,整個模式巧妙地處于一個方面中。(我排除了 IncrementMonitor 助手類,因為它在模式中沒有起到結構化的作用。)在 Java 語言版本中,基本模式實現(xiàn) (不考慮客戶代碼)要求具有三個類。這有什么效果呢? 易于理解:因為 AspectJ 的切入點語言的威力,方面可以使用同一個通知影響多個操作。對比之下,修飾器類必須在每一個操作上重復該行為。Sun 實現(xiàn)的代碼行數(shù)是方面實現(xiàn)的兩倍多,其中部分是由于上述原因。ProgressMonitorInputStream 大約是 110 行,而 FilterInputStream 是 40 行(我放過了 InputStream,因為它可能是修飾器模式中的合法父類)。對比之下,MonitorFileReads 方面用了 53 行,而 IncrementMonitor 助手類用了 12 行。行的比率是 160 比 65,或者大約是 2.4 : 1。雖然 LOC 只是一個粗略的測量方法,但是一般來說,代碼越短就會越清晰。 而且,如果您熟悉 AOP,那么 AspectJ 解決方案不會給您留下正在運行什么特殊事情的感覺。Java 語言解決方案要求幾個類小心地進行協(xié)作,而 AspectJ 版本看起來就像是正在進行大多數(shù)方面所做的工作:即通過通知將行為添加到一組連接點上。 最后,值得記住的是 AOP 的經常遭批評的一個缺點:再也不能通過閱讀源代碼了解模塊將做什么工作了。如果把修飾器應用到對象上,并且沒有方面的幫助,那么在客戶代碼(不是包裝的位置) 或者在對象顯示附加行為的修改目標中(FileInputStream),都沒有基于源代碼的線索。對比之下,如果在 AJDT 中檢查清單 2 的 GUI,那么可以看到在行 while (in.read(b) != -1) 上的友好注釋,這些注釋指明監(jiān)視器方面將影響讀取調用。可以將 AspectJ 與它的開發(fā)環(huán)境結合,在這種情況,要比原先的實現(xiàn)提供更多的信息。 重用:由于修飾構建到了語言中,幾乎所有方面都“重用”這個模式。更具體的重用可能是:讓監(jiān)視方面變得更抽象,允許子方面指定監(jiān)視操作的切入點。通過這種方式,差不多所有對象都能用監(jiān)視進行修飾 不再需要進行傳統(tǒng)實現(xiàn)要求的那些準備工作。(如果想了解抽象方面,本文的 第 2 部分 將更詳細地解釋它們的用途。) 維護:向對象添加新修飾不需要特殊的努力。如果修飾目標變了(想像一下新的讀方法),那么(可能)必須對切入點進行更新,以體現(xiàn)這種變化。必須更新切入點這一點有些累人,但是通過編寫能夠捕捉新操作的強壯切入點,可以減輕這個負擔。(請參閱 參考資料,獲得關于強壯切入點的一個 blog 的鏈接。)在任何情況下,更新切入點看起來都要比更新所有修飾器(就像在 Java 語言實現(xiàn)中進行類似更改所要求的那樣)的麻煩少一些。 這里是另一個有趣的場景(在前面 Java 語言分析中提到過):監(jiān)視 所有的 文件讀取。使用 OO 修飾器,這就意味著讀取流的每個類都必須記得把自己包裝在 ProgressMonitorInputStream 中。對比之下,MonitorFileReads 方法會監(jiān)視任何輸入流上的讀取,只要它們是在 JComponent 的控制流程中發(fā)生的。由于 ProgressMonitor 只在操作進行的時間比當前閾值大的時候才彈出,所以這個方面可以透明地保證用戶在必須等候文件讀取時永遠不會被打擾 程序員無需對此警惕。 組合:像競爭性實現(xiàn)一樣,AspectJ 版本支持用很少的工作就可以透明地組合多個修飾器。 正如我前面提到過的,修飾器的主要秘密(透明地把行為添加到操作)包含在 AspectJ 語言中。AspectJ 實現(xiàn)的惟一挑戰(zhàn)是如何把方面的狀態(tài)(更新的進度監(jiān)視器)與特定實例關聯(lián) 示例使用 map 實現(xiàn)這個關聯(lián)。處理關聯(lián)關系的需要使得修飾器作為模式保留在 AspectJ 中。有時,當修飾的機制已經存在的時候,使用傳統(tǒng)的修飾器看起來更容易一些 特別是模式沒有入侵已修飾的類時。但是,如果修飾機制不存在,那么 AspectJ 實現(xiàn)的靈活性和簡單性就使其成為更好的選擇。 結束語 我希望對這兩個熟悉的模式的介紹有助于表現(xiàn)面向方面機制的實際應用。隨著開發(fā)社區(qū)與不斷涌現(xiàn)的泛濫的范式斗爭,把新技術應用到老問題上(已經存在良好解決方案的問題)可能會有用 這樣的練習有助于用熟悉的方式評估新技術。 那么,迄今為止這方面進行得如何呢?雖然不是一個金錘(golden hammer),但 AspectJ 已經成功地保護了一些用來實現(xiàn)傳統(tǒng) OO 模式的固有優(yōu)勢。這些優(yōu)勢來自于 AspectJ 能夠更好地處理橫切關注點的能力。通過把模式的代碼搜集到單一方面中,AspectJ 使得通過閱讀代碼來理解模式變得更容易。因為模式代碼沒有在非模式類(例如適配器和修飾器要求的包裝位置)中顯示,所以其他這些類也很容易理解。這種組合還使得擴展和維護系統(tǒng)變得更加容易,甚至使到處重用模式也變得更加容易。 適配器和修飾器模式代表中等復雜的模式。在系列文章的 第 2 部分 中,我將研究是否可以將面向方面擴展到更復雜的模式。具體地說,第 2 部分將研究觀察者模式,其中包括多個角色和動態(tài)關系。在第 2 部分中,還將探索面向方面的重用 把模式或協(xié)議定義為抽象方面并用特定于應用程序的方面來應用它的能力。 1用AspectJ增強設計模式下在這篇文章的第1 部分中,我從面向方面的角度研究了兩個廣泛應用的面向對象設計模式。在演示了適配器和修飾器模式在 Java 系統(tǒng)和 AspectJ 系統(tǒng)中的實現(xiàn)方式之后,我從代碼理解、重用、維護性和易于組合幾方面考慮了每種實現(xiàn)的效果。在兩種情況下,我發(fā)現(xiàn)橫切 Java 實現(xiàn)模塊性的模式在 AspectJ 實現(xiàn)中可以組合到單獨的一個方面中。理論上,這種相關代碼的協(xié)同定位可以使模式變得更易理解、更改和應用。用這種方式看模式,就轉變對 AOP 的一個常見批評 阻止開發(fā)人員通過閱讀代碼了解代碼的行為。在這篇文章的第 2 部分中,我將通過深入研究觀察者(Observer)模式,完成我對 Java 語言的模式實現(xiàn)和 AspectJ 模式實現(xiàn)的比較。 我選擇把重點放在觀察者(Observer)模式上,因為它是OO設計模式的皇后。該模式被人們廣泛應用(特別是在 GUI 應用程序中),并構成了 MVC 架構的關鍵部分。它處理復雜的問題,而在解決這類問題方面表現(xiàn)得相對較好。但是,從實現(xiàn)需要的努力和代碼理解的角度來說,它還是帶來了一些難以解決的難題。與修飾器或適配器模式不同(它們的參與者主要是為模式新創(chuàng)建的類),觀察者(Observer)模式要求您先侵入系統(tǒng)中現(xiàn)有的類,然后才能支持該模式 至少在 Java 語言中是這樣。 方面可以降低像觀察者(Observer)模式這種侵入性模式的負擔,使得模式參與者更靈活,因為不需要包含模式代碼。而且,模式本身可以變成抽象的基本方面,允許開發(fā)人員通過導入和應用它來實現(xiàn)重用,不必每次都要重新考慮模式。為了查看這些可能性如何發(fā)揮作用,我將繼續(xù)本文第一部分設置的格式。我將從示例問題開始,提供對觀察者(Observer)模式的通用描述。然后我將描述如何用 AspectJ 和 Java 語言實現(xiàn)觀察者(Observer)模式。在每個實現(xiàn)之后,我將討論是什么造成模式的橫切,模式的這個版本在理解、維護、重用和組合代碼方面有什么效果。 在繼續(xù)后面的討論之前,請單擊本頁頂部或底部的 代碼 圖標,下載本文的源代碼。 觀察者(Observer)模式 根據(jù) Portland Pattern Repository Wiki(請參閱 參考資料 一節(jié),獲得有關的細節(jié)),觀察者(Observer)模式的用途是定義對象之間的一對多依賴關系,因此,當一個對象的狀態(tài)發(fā)生改變時,其所有依賴項都會得到通知,并自動更新。這使得觀察者適用于所有類型的通知需要。請考慮以下情況: 關于本系列 AOPWork 系列是為具有一定面向方面的編程背景、并準備擴展或者加深其知識的開發(fā)人員準備的。與大多數(shù)developerWorks 文章一樣,本系列具有很高實用性:從每一篇文章中學到的知識立刻就能使用得上。 所挑選的為這個系列撰稿的每一位作者,都在面向方面編程方面處于領導地位或者擁有這方面的專業(yè)知識。許多作者都是本系列中討論的項目或者工具的開發(fā)人員。每篇文章都經過仔細審查,以確保所表達的觀點的公平和準確。 關于文章的意見和問題,請直接與文章的作者聯(lián)系。如果對整個系列有意見,可以與本系列的組織者 Nicholas Lesiecki 聯(lián)系。更多關于 AOP 的背景知識,請參閱 參考資料。 條形圖可以觀察它顯示的數(shù)據(jù)對象,以便在這些對象變化時對它們進行重新繪制。 Acc
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 財務人員的責任與義務計劃
- 制定清晰的財務目標計劃
- 幼兒藝術表現(xiàn)的多樣性研究計劃
- 萬源市竹源煤業(yè)有限公司長石二煤礦礦山地質環(huán)境保護與土地復墾方案情況
- 2025年經典大班蒙氏數(shù)學標準教案
- 空乘禮儀知識培訓班課件
- 2025年四川貨運從業(yè)資格證考試模擬考試答案
- 胃癌治療手段
- 2025年洛陽貨運從業(yè)資格證考試技巧
- 3D打印技術知到課后答案智慧樹章節(jié)測試答案2025年春上海電子信息職業(yè)技術學院
- 辦公用品供貨服務計劃方案
- DB37∕T 5107-2018 城鎮(zhèn)排水管道檢測與評估技術規(guī)程
- 2022新冠疫苗疑似預防接種異常反應監(jiān)測和處置方案
- 酒精溶液體積濃度、質量濃度與密度對照表
- 主要腸內營養(yǎng)制劑成分比較
- 老年人各系統(tǒng)的老化改變
- 小學五年級綜合實踐課教案
- 煤礦井下供電常用計算公式及系數(shù)
- ISO14001:2015中文版(20211205141421)
- 汽車總裝車間板鏈輸送線的應用研究
- 工作日志模板
評論
0/150
提交評論