




已閱讀5頁,還剩135頁未讀, 繼續(xù)免費(fèi)閱讀
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
用AspectJ增強(qiáng)設(shè)計(jì)模式上設(shè)計(jì)模式長期以來一直是一些經(jīng)驗(yàn)豐富的開發(fā)人員的工具箱的重要組成部分。不幸的是,因?yàn)槟J娇梢杂绊懚鄠€(gè)類,所以它們也是侵入性的、難于使用(和重用)。本文是 AOPWork 系列的第 3 部分,是一篇由兩部分組成的文章,在這篇文章中,Nicholas Lesiecki 將介紹 AOP 是怎樣通過根本轉(zhuǎn)變模式實(shí)現(xiàn)來解決這一問題的。他研究了三個(gè)經(jīng)典的 Gof 設(shè)計(jì)模式 (適配器模式、修飾器模式和觀察者模式),同時(shí)還將討論使用面向方面技術(shù)實(shí)現(xiàn)這些模式所帶來的實(shí)踐和設(shè)計(jì)方面的好處。 什么是設(shè)計(jì)模式?根據(jù) Design Patterns: Elements of Reusable Object-Oriented Software: 設(shè)計(jì)模式系統(tǒng)地命名、促進(jìn)和解釋了解決面向?qū)ο笙到y(tǒng)中重復(fù)出現(xiàn)的設(shè)計(jì)問題的一個(gè)通用設(shè)計(jì)。它描述了問題、解決方案、何時(shí)應(yīng)用該解決方案以及所產(chǎn)生的結(jié)果。它還提供了一些實(shí)現(xiàn)提示和示例。解決方案是解決問題的對(duì)象和類的總體安排??梢远ㄖ撇?shí)現(xiàn)解決方案,解決具體上下文環(huán)境中的問題。 在多年成功地應(yīng)用模式解決 OO 系統(tǒng)中的問題之后,我發(fā)現(xiàn)自己也認(rèn)同了這個(gè)定義。模式是與普通程序員談?wù)撛O(shè)計(jì)的最好方式,它們代表解決“重復(fù)出現(xiàn)的設(shè)計(jì)問題”的最佳實(shí)踐。所以,當(dāng)我參加了 Stuart Halloway 的一次訪談時(shí),我感到有點(diǎn)震驚:他為 GoF 提供了另一個(gè)頭銜:“處理 C+ 中破損事物的修理廠”。他的觀點(diǎn)是:在一種語言中以“模式”方式存在的東西,在不同的范式下,可以融入語言本身。接著他給出了 Factories 的示例 該示例在 Java 語言中有用,但在 Objective-C 中卻沒多大用,后者支持從構(gòu)造函數(shù)中返回子類型。 我思考了很長一段時(shí)間,然后我認(rèn)識(shí)到兩個(gè)方面實(shí)際上說的是同一件事之前:設(shè)計(jì)模式提供了表達(dá)那些無法直接在編程語言中表達(dá)的概念的詞匯表。 那么,AOP 位居何處呢?對(duì)于 OOP,我們有 GoF 模式,它提供了處理公共的概念(像觀察者和修飾器)的統(tǒng)一方法(盡管有時(shí)有點(diǎn)麻煩)。AOP 構(gòu)建在 OOP 之上,提供了表達(dá)橫切關(guān)注點(diǎn)的直接方式。它認(rèn)為某些 GoF 模式是關(guān)于橫切的,可以直接用 AOP 表示。所以您會(huì)注意到,對(duì)于一些包含許多類的模式,用一個(gè)方面就可以表達(dá)。有些模式變得更易使用,因?yàn)樗鼈兛梢园俅a。有些模式得到了非常好的支持,以致于它們幾乎消失不見。其他模式嚴(yán)格綁定到 OOP (例如處理類結(jié)構(gòu)的模式),所以它們?cè)谂c AOP 結(jié)合使用的時(shí)候保持不動(dòng)。 本文的目的是探索用 AOP (特別是用 AspectJ)進(jìn)行的模式實(shí)現(xiàn)。我選擇 GoF 模式,是因?yàn)樗且粋€(gè)非常流行和通用的工具。在本文的第 1 部分中,我要設(shè)置一些分析模式影響的指標(biāo),然后研究適配器和修飾器模式。適配器會(huì)演示靜態(tài)交叉的優(yōu)勢(shì),而修飾器則會(huì)暴露它自身是一個(gè)正在消失的模式。在 第 2 部分中,我將提供對(duì)觀察者模式更加深入的研究,這種模式并沒有消失,但您會(huì)看到在用 AspectJ 實(shí)現(xiàn)它時(shí)的一些主要好處。第 2 部分將顯示 AspectJ 如何使模式轉(zhuǎn)變成可重用的基本方面,從而允許您下載預(yù)先構(gòu)建好的模式庫 這是讓模式愛好者們興奮的一大優(yōu)勢(shì)。 1為什么把 AOP 應(yīng)用到設(shè)計(jì)模式? 我前面說過,許多模式都是橫切的,當(dāng)然我不是第一個(gè)想到這一點(diǎn)的人。最近有一篇研究論文對(duì) GoF 模式進(jìn)行了分析,分析發(fā)現(xiàn):在 23 個(gè)模式中,有 17 個(gè)模式表現(xiàn)出某種程度的橫切。(這篇論文是 Jan Hannemann 和 Gregor Kiczales 合著的“Java AspectJ 中的設(shè)計(jì)模式實(shí)現(xiàn)”,請(qǐng)參閱 參考資料 一節(jié),以獲取更多細(xì)節(jié)。) 如果 AOP 承諾可以協(xié)助解決橫切,那么在設(shè)計(jì)模式上使用 AOP 有什么好處呢?我先從用通用術(shù)語回答這個(gè)問題開始,然后設(shè)置一個(gè)框架,通過它來考察每個(gè)設(shè)計(jì)模式。 在設(shè)計(jì)模式上使用 AOP 的好處 AOP 第一個(gè)關(guān)鍵的好處是把指定設(shè)計(jì)模式的代碼 本地化 的能力。這意味著通常只在一個(gè)方面或一對(duì)密切關(guān)聯(lián)的方面上就可以實(shí)現(xiàn)模式。(這與 Java 語言實(shí)現(xiàn)形成對(duì)比,在 Java 語言實(shí)現(xiàn)中,模式應(yīng)用程序可以分布在多個(gè)類中。)能夠在一個(gè)地方看到所有代碼會(huì)帶來幾個(gè)實(shí)際的好處。首先,如果模式的所有交互都能在一個(gè)地方看到的話,那么閱讀代碼的人就能更容易地理解模式。其次,如果開發(fā)人員需要改變模式的實(shí)現(xiàn),那么他或她就能在一個(gè)地方修改模式,而不用在整個(gè)系統(tǒng)中追蹤模式的片斷。最后,開發(fā)人員可以用有意義的名稱描述封裝模式的方面,為以后的維護(hù)人員提供有關(guān)模式意圖的文字線索。例如,可以把方面命名為 SensorAdapter,這表明在傳感器上使用的是適配器模式。 AspectJ 模式實(shí)現(xiàn)的另一個(gè)關(guān)鍵好處就是 遺忘性(obliviousness)。換句話說,在模式中發(fā)揮作用的類不必知道這個(gè)角色。這個(gè)好處直接來自本地化 因?yàn)槟J绞窃谀骋粋€(gè)方面實(shí)現(xiàn)本地化的,所以不需要冒犯其參與者。遺忘性讓模式參與者更容易理解代碼。不僅如此,遺忘性還讓模式更容易組合。在 Java 語言實(shí)現(xiàn)中,如果類參與到多個(gè)模式中,模式的機(jī)制會(huì)迅速模糊它的核心含義。如果類不知道那些在模式中的參與者,那么可以在其他上下文中重用這些類。在本文的 第 2 部分 中將看到這方面的一個(gè)具體示例。 這些好處允許對(duì)某些模式實(shí)現(xiàn)代碼級(jí)重用。設(shè)計(jì)模式的概念和結(jié)構(gòu)一直都是可重用的。如果想實(shí)現(xiàn)觀察者模式,任何人都可以找出 GoF 的書籍,把模式應(yīng)用到自己的代碼中。但是,如果使用面向方面技術(shù),那么通過下載 ObserverProtocol 方面 (可以在設(shè)計(jì)模式計(jì)劃中獲得它,請(qǐng)參閱參考資料 一節(jié)),就可以避免這個(gè)麻煩。除了節(jié)省的實(shí)現(xiàn)工作之外,代碼級(jí)重用還允許模式代碼和文檔進(jìn)行更緊密的耦合。例如,我可以瀏覽 ObserverProtocol 的 javadoc,不用另找一本書就可以理解它的意圖和結(jié)構(gòu)。 分析框架 每個(gè)模式的描述都跟著一個(gè)公共結(jié)構(gòu)。我將從一個(gè)示例問題開始介紹,提供對(duì)這個(gè)模式的通用描述。然后我會(huì)描述如何實(shí)現(xiàn)這個(gè)模式,先使用 Java 語言實(shí)現(xiàn)它,然后使用 AspectJ 語言實(shí)現(xiàn)它。在每個(gè)實(shí)現(xiàn)后面,我都會(huì)描述是什么造成了模式的橫切,以及這個(gè)版本的模式在理解、維護(hù)、重用和編排代碼的時(shí)候有什么效果。1適配器模式 我要詳細(xì)考慮的第一個(gè)模式就是適配器模式。適配器模式完全是關(guān)于兼容性的。這個(gè)模式允許類與其他原來由于接口不兼容而無法交互的類進(jìn)行交互。要在 Java 代碼中實(shí)現(xiàn)適配器,需要用特殊的適配器類包裝目標(biāo)類,適配器類能把目標(biāo)類的 API 轉(zhuǎn)換成客戶想要的 API,或者轉(zhuǎn)換成能夠更容易利用的 API。 設(shè)置:提供一個(gè)聚合的傳感器讀取器 假設(shè)現(xiàn)在要設(shè)計(jì)一個(gè)航天器,則需要提供一個(gè)讀取器,讀取航天器上所有關(guān)鍵傳感器。由于是在擴(kuò)展現(xiàn)有系統(tǒng),所以對(duì)于每個(gè)要訪問的傳感器而言,都存在方便的 Java 類。例如,可以用以下類訪問溫度計(jì): public class TemperatureGaugepublic int readTemperature()/accesses sensor internals 可以用以下類訪問輻射傳感器:public class RadiationDetector public double getCurrentRadiationLevel()/read radiation somehow 這些傳感器類中有一些是其他團(tuán)隊(duì)成員編寫的,有一些是第三方供應(yīng)商編寫的?,F(xiàn)在想要做的是提供一個(gè)顯示方式,顯示每個(gè)傳感器的狀態(tài),這樣司令官只要看一眼,就知道飛船是否有問題。以下是所需要功能的一個(gè)示例。(實(shí)際的顯示可能包含閃爍的紅燈和高音喇叭,但現(xiàn)在我們只顯示文本。) Readout: Sensor 1 status is OK Sensor 2 status is OK Sensor 3 status is BORDERLINE Sensor 4 status is DANGER! 可以用以下方法實(shí)現(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) 檢測(cè)就能讀出每個(gè)傳感器呢?選項(xiàng)將修改每個(gè)傳感器類,讓它們擁有 getStatus() 方法,以便解釋傳感器的讀取操作,并返回 String,如下所示。 if(this.readTemperature() 160)return DANGER;return OK 這樣做會(huì)將一些不相關(guān)的狀態(tài)顯示代碼帶到多個(gè)類當(dāng)中,增加它們的復(fù)雜性。而且,可能存在一些實(shí)際限制 (例如必須重新編譯第三方類)。這就是適配器模式發(fā)揮作用的地方。 1Java 語言的適配器 適配器模式的傳統(tǒng)實(shí)現(xiàn)方式是:用一個(gè)實(shí)現(xiàn)了方便的 API 的類來包裝每個(gè)目標(biāo)。在這種情況下,要?jiǎng)?chuàng)建一個(gè)公共接口,比如 StatusSensor,如下所示: public interface StatusSensorString getStatus(); 有這個(gè)公共接口存在,就可以像以下這樣實(shí)現(xiàn)讀取器方法: for (StatusSensor sensor : allSensors) System.out.print(Sensor + count+);System.out.println( status is + sensor.getStatus(); 剩下的惟一挑戰(zhàn)就是讓每個(gè)傳感器符合這個(gè)接口。適配器類可以實(shí)現(xiàn)這一點(diǎn)。在清單 1 中可以看到,每個(gè)適配器都以成員變量的形式保存自己包裝的傳感器,用這個(gè)底層的傳感器實(shí)現(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); 清單還顯示了在讀取器之前用適當(dāng)?shù)倪m配器包裝每個(gè)傳感器的“膠水代碼”。模式并沒有指定這個(gè)膠水代碼應(yīng)當(dāng)在哪個(gè)具體位置出現(xiàn)。可能的位置包括“創(chuàng)建之后”和“使用之前”??梢詫⑹纠a放在向讀取器集合添加傳感器之前。1對(duì) Java 語言適配器的分析 適配器模式在傳統(tǒng)的實(shí)現(xiàn)中運(yùn)用得很成功。但是什么讓它產(chǎn)生橫切呢?是“狀態(tài)”關(guān)注點(diǎn)橫切了多個(gè)不同的傳感器類。如果想?yún)f(xié)同定位一個(gè)包中的適配器類,那么這個(gè)模式的 OO 實(shí)現(xiàn)會(huì)很好地得到模塊化。包會(huì)成為“適配器模塊”。包裝術(shù)語將阻止傳感器了解模式,從而形成更加松散的耦合。不幸的是,實(shí)現(xiàn)包裝任務(wù)的那部分應(yīng)用程序既需要知道適配器,還需要知道應(yīng)用適配器的傳感器。因此,模式會(huì)造成“膠水代碼”的位置成為橫切。 現(xiàn)在,讓我們來看看 Java 語言適配器是如何在我的評(píng)價(jià)指標(biāo)上堆疊起來的: 易于理解:在包中協(xié)同定位的統(tǒng)一命名的 SensorAdapter,使得這個(gè)模式的意圖清晰。不幸的是,膠水代碼的位置可能遠(yuǎn)在適配器之外。由于膠水代碼的區(qū)域不是結(jié)構(gòu)化的,所以在試圖理解模式的時(shí)候會(huì)忽略它,或者在試圖理解它處理的代碼時(shí)忽略它。 您還必須關(guān)注對(duì)象標(biāo)識(shí)的處理問題。也就是說,如果同一對(duì)象包裝和未包裝的版本同處在一個(gè)系統(tǒng)中,那么必須想好是否應(yīng)當(dāng)對(duì)它們同等對(duì)待。 重用:要重用這個(gè)模式,就必須從頭開始重新實(shí)現(xiàn)該模式。 維護(hù):在向讀取器添加新傳感器時(shí),必須添加新的適配器類,并更新包裝它們的膠水代碼。 組合:假設(shè)想要在另一個(gè)模式中包含傳感器。由于適配器已經(jīng)忘記傳感器,所以它們不受影響。但這是把雙刃劍。新的模式應(yīng)當(dāng)把適配版本的傳感器當(dāng)作自己的對(duì)象,還是應(yīng)該將未適配版本的傳感器當(dāng)作自己的對(duì)象呢? AspectJ 適配器 像使用其他設(shè)計(jì)模式一樣,適配器的 AspectJ 實(shí)現(xiàn)保留了它的同類的意圖和概念。這個(gè)實(shí)現(xiàn)采用類型間聲明,這是一個(gè)重要的橫切類型支持,要比切入點(diǎn)和通知(advice)的啟動(dòng)時(shí)間更少。如果需要對(duì)靜態(tài)橫切進(jìn)行回顧,請(qǐng)參閱 參考資料 一節(jié),以獲得適當(dāng)?shù)闹甘尽?像使用純 OOP 版本一樣,AOP 版本的適配器需要 StatusSensor 接口。但是,AspectJ 版本沒有采用獨(dú)立的包裝器類,而是采用 聲明父母 的形式,讓不同的傳感器直接實(shí)現(xiàn) StatusSensor,如下如示: public aspect SensorAdapter declare parents :(TemperatureGauge | RadiationDetector)implements StatusSensor; 現(xiàn)在傳感器應(yīng)當(dāng)符合接口。但是它們還沒有 實(shí)現(xiàn) 接口 (這是 AspectJ 編譯器會(huì)很高興指出的一個(gè)事實(shí))。要完成模式的實(shí)現(xiàn),必須向方面添加要使每個(gè)傳感器符合要求的類型間方法聲明。下面的代碼把 getStatus() 方法添加到 TemperatureGauge 類中: public String TemperatureGauge.getStatus()if(this.readTemperature() 160)return DANGER;return OK; AspectJ 版本的讀取器類看起來與用 Java 語言實(shí)現(xiàn)的版本相同,除了不必用膠水代碼包裝傳感器。每個(gè)傳感器同時(shí)又是自己的包裝器。 AspectJ 適配器的分析 用 AspectJ 實(shí)現(xiàn)適配器的關(guān)鍵好處是本地化。整個(gè)模式都包含在一個(gè)方面中,而不是兩個(gè)以上的獨(dú)立適配器和非結(jié)構(gòu)化的“包裝”位置。這里是根據(jù)我的指標(biāo)考察的 AspectJ 實(shí)現(xiàn)的情況: 易于理解:沒有包裝,理解模式只需查看適配器方面即可,這消除了四處查看的需要。沒有包裝還消除了處理對(duì)象標(biāo)識(shí)問題的需要。 重用:AspectJ 版本與其他版本具有同樣的可重用性。 維護(hù):因?yàn)槊總€(gè)新傳感器只包括編寫一個(gè)方法(而不是完整的類),所以擴(kuò)展 AspectJ 實(shí)現(xiàn)應(yīng)當(dāng)略微容易些。隨著適配器的數(shù)量增長或者每個(gè)適配要求的邏輯變復(fù)雜,可能會(huì)發(fā)現(xiàn)單一的方面會(huì)變得不合理地長。在這種情況下,可以把一個(gè)方面拆分成幾個(gè)方面。拆分方面會(huì)損失本地化的好處,但是可以保留其他好處。 組合:可以很容易地組合多個(gè)模式,因?yàn)椴恍枰幚戆b協(xié)調(diào)的問題。 結(jié)果是:Java 和 AspectJ 實(shí)現(xiàn)在處理傳感器類的方式上都做得不錯(cuò)。但是,只有 AspectJ 版本處理了應(yīng)用程序的其他方面。這是一個(gè)主要優(yōu)勢(shì)嗎?這可能取決于應(yīng)用程序是否會(huì)表現(xiàn)出分析中描述的復(fù)雜屬性。如果我正對(duì)某一個(gè)項(xiàng)目使用 AspectJ,那么我肯定會(huì)用它實(shí)現(xiàn)適配器,雖然我介紹 AspectJ 不僅僅是為了解決這個(gè)問題。下一個(gè)模式,即修飾器模式,提供了一些更引人注目的優(yōu)勢(shì)。修飾器模式 從面向方面的角度來考慮,修飾器是一個(gè)有趣的模式,因?yàn)樗墙咏跋А钡哪J街?,具有面向方面的語言(例如 AspectJ)所引入的能力。為什么這么說呢?如果深入研究修飾器在面向方面和面同對(duì)象實(shí)現(xiàn)中的演變,就會(huì)讓事情變得更清晰。 修飾器模式的目標(biāo)是動(dòng)態(tài)地把功能添加到現(xiàn)有對(duì)象上。GoF 書中給出的規(guī)范示例包括文字修飾。在他們的示例中,GUI 組件類是在一個(gè)修飾器類中包裝的,可以添加邊框或者是滾動(dòng)條來顯示該組件。Java 類庫明顯非常支持修飾器,允許用java.util.Collections 的方法修飾 Collection,這樣,集合就變成不可修改的或者是同步的,還有各種各樣的 IO 流,它們可以緩沖、擴(kuò)大或者監(jiān)視其他流。 設(shè)置:監(jiān)視文件讀取 為了給這個(gè)示例增加一些趣味,我選擇了 Java 發(fā)行包中的修飾器,用它來查看用 AspectJ 復(fù)制一個(gè)修飾器時(shí)需要做些什么。其中一個(gè)我覺得有趣的修飾器是來自 javax.swing 的 ProgressMonitorInputStream。根據(jù)記錄,ProgressMonitorInputStream 監(jiān)視底層輸入流的讀取進(jìn)度。 為了演示修飾的實(shí)際效果,我編寫了一個(gè)簡單的讀取文件的 GUI??梢栽谇鍐?2 中查看讀取輸入流的代碼,還可以在圖 1 看到運(yùn)行的應(yīng)用程序的截屏。(也可以單擊這一頁頂部或底部的 代碼 圖標(biāo),研究示例的完整源代碼。) 圖 1. 流的 ProgressMonitor 在考慮下面一節(jié)的時(shí)候,您可能想擁有 java.io 和 ProgressMonitorInputStream javadoc 或者源代碼,請(qǐng)參閱 參考資料,以獲得更多參考消息。1Java 語言的修飾器 在 Java 語言中,最初是通過創(chuàng)建 AbstractComponent 接口 (或類)認(rèn)識(shí)修飾器模式的,基本實(shí)現(xiàn) (有時(shí)稱為 ConcreteComponent) 和修飾器都要符合這一點(diǎn)。在這個(gè)示例中,AbstractComponent 是 java.io.InputStream,它定義了 FileInputStream (ConcreteComponent) 和 BufferedInputStream (ConcreteDecorator) 的接口。 雖然并不是嚴(yán)格要求的,但修飾器實(shí)現(xiàn)通常提供一個(gè) AbstractDecorator,它維護(hù)一個(gè)對(duì)已修飾組件的引用,并在不添加額外行為的情況下提供基本的修飾機(jī)制。在 java.io 中,F(xiàn)ilterInputStream 提供了這項(xiàng)功能。最后,ConcreteDecorator 擴(kuò)展了 AbstractDecorator,覆蓋了需要修飾的方法,并在調(diào)用已修飾組件上的相同方法之前或之后添加行為。在這種情況下,ProgressMonitorInputStream 使用了 ConcreteDecorator。 看一看 Sun 的 ProgressMonitorInputStream 實(shí)現(xiàn)(由于許可考慮,我在這里不再重新打?。?,可以看到它在創(chuàng)建 javax.swing.ProgressMonitor 時(shí)實(shí)例化了該監(jiān)視器。在每個(gè) read 方法后面,它用從底層流中讀取的字節(jié)數(shù)更新監(jiān)視器。ProgressMonitor 類決定了什么時(shí)候彈出監(jiān)視對(duì)話框并更新可視顯示。 要使用 ProgressMonitorInputStream,只需要包裝另外一個(gè)輸入流(如清單 2 所示),并確保在進(jìn)行讀取操作時(shí)引用已包裝的實(shí)例即可。在這里,請(qǐng)注意適配器和修飾器模式之間的相似性:兩者都需要以編程方式對(duì)目標(biāo)類應(yīng)用額外的行為。 清單 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 語言修飾器的分析 查看以上示例之后,看起來沒有東西比使用修飾器模式更簡單了。但是,不要忘記,為了使這個(gè)示例能夠運(yùn)行,Sun 實(shí)現(xiàn)了InputStream、FilterInputStream 和 ProgressMonitorInputStream 代碼的數(shù)量可不是微不足道的。 在這個(gè)示例中,監(jiān)視的關(guān)注點(diǎn)橫切了 InputStream。更加通用的情況是,修飾可以橫切修飾的目標(biāo)。更進(jìn)一步地說,修飾關(guān)注點(diǎn)可以橫切應(yīng)用程序。例如,用戶可能要求一個(gè)針對(duì) 全部 文件輸入的 ProgressMonitor。(為了避免您把它當(dāng)作一個(gè)人為的示例,請(qǐng)自己問一下自己,有多少次您使用輸入流的時(shí)候,沒有 對(duì)其進(jìn)行緩沖。) 現(xiàn)在看一下剩下的指標(biāo): 易于理解:一旦了解了修飾器的工作,就很容易理解它。但是我永遠(yuǎn)不會(huì)忘記我在第一次打開 java.io 并試圖了解組成應(yīng)用于流上的機(jī)器類(machinery class)的健康情況時(shí)所產(chǎn)生的迷惑。雖然一份快速培訓(xùn)教程可以很容易地讓我擺正方向,但這只適用于查看代碼,沒有理解模式的簡易途徑。更具體地度量理解方面的負(fù)擔(dān)的方法是計(jì)算代碼行數(shù)。在研究完 AspectJ 實(shí)現(xiàn)之后,我將看一下行數(shù)。不過,還是一點(diǎn)價(jià)值都沒有,由于修飾器使用了包裝功能,所以它也會(huì)遭遇那些影響適配器的對(duì)象標(biāo)識(shí)問題。 重用:要重用這個(gè)模式實(shí)現(xiàn),必須重新實(shí)現(xiàn)它。 維護(hù):有兩個(gè)關(guān)鍵的維護(hù)場(chǎng)景:在第一個(gè)場(chǎng)景中,要向現(xiàn)有實(shí)現(xiàn)添加新的修飾器。根據(jù)修飾器中方法的數(shù)量,這項(xiàng)工作可能很冗長,但顯然不是很難。在第二個(gè)場(chǎng)景中,要向 AbstractComponent 添加新操作(即 InputStream)。這意味著要更新所有現(xiàn)有修飾器,以反映新的操作,還要針對(duì)每個(gè)修飾器作出決策,決定是不是應(yīng)當(dāng)把修飾應(yīng)用到新方法上。 組合:因?yàn)樾揎椘骱徒M件共享公共接口,所以修飾器允許在指定實(shí)例上透明地組合修飾器。(參見清單 2,在那里,代碼緩沖也將監(jiān)視輸入流)。這非常好,特別是因?yàn)樾揎椀哪繕?biāo)不必知道自己受到了修飾。1AspectJ 修飾器 在 Hanneman 和 Kiczales 合著的論文中,他們談到了以下這點(diǎn): 如果使用 AspectJ,那么某些模式的實(shí)現(xiàn)就會(huì)完全消失,因?yàn)?AspectJ 語言的構(gòu)造直接實(shí)現(xiàn)了它們。這適用于 修飾器。 查看一下 Gof 書中關(guān)于使用修飾器的動(dòng)機(jī)的那一節(jié),然后您就會(huì)很清楚為什么會(huì)是這種情況了: 修飾器把請(qǐng)求轉(zhuǎn)發(fā)到組件,在轉(zhuǎn)發(fā)前后可能執(zhí)行附加動(dòng)作(例如繪制邊框)。透明性支持遞歸地嵌套修飾器,所以它支持無限數(shù)量的附加功能。 那么所給的建議是什么呢?是透明地把附加“操作”添加到任何操作上的能力?從某種意義上說,AspectJ 支持對(duì)任何方法進(jìn)行修飾。為了看到它在真實(shí)系統(tǒng)中的作用,可以研究 AspectJ 實(shí)現(xiàn)的輸入流讀取監(jiān)視。 識(shí)別被修飾的操作 為了正確地實(shí)現(xiàn)監(jiān)視,方面必須識(shí)別出流上的讀取操作。通過編寫一個(gè)撿取讀取取操作的切入點(diǎn)可以做到這一點(diǎn): pointcut arrayRead() :call(public int InputStream+.read(.); 現(xiàn)在可以應(yīng)用一些下面這樣通用格式的通知: after() returning (int bytesRead) :arrayRead()updateMonitor(bytesRead); 此通知用 returning 格式公開方法調(diào)用的返回值。讀取的字節(jié)數(shù)被傳遞給方面上的一個(gè)私有方法:updateMonitor()。該方法負(fù)責(zé)更新實(shí)際的 ProgressMonitor 的細(xì)節(jié)(稍后會(huì)有更多關(guān)于這項(xiàng)工作的內(nèi)容)。 公開相關(guān)上下文 到目前為止,解決方案都很簡單。但是,這表明 ProgressMonitor 類還要求做幾件事才能實(shí)現(xiàn)它的工作。具體地說,它需要一個(gè) GUI 組件來綁定監(jiān)視對(duì)話模式。在傳統(tǒng)的實(shí)現(xiàn)中可以看到這個(gè)要求: /this is a JPanel GUI component stream = new ProgressMonitorInputStream(this, This is gonna take a while, stream); 要獲得需要的 GUI 組件,方面必須把它綁定到切入點(diǎn),通知才能使用它。清單 3 包含修訂后的切入點(diǎn)和通知。注意,fromAComponent() 切入點(diǎn)利用了原始的 cflow() 切入點(diǎn)。切入點(diǎn)實(shí)際上是在說“選擇作為 JComponent 上面方法執(zhí)行結(jié)果的全部連接點(diǎn),并公開這個(gè)組件,供通知使用?!?清單 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); 維護(hù)狀態(tài) 為了讓方面的應(yīng)用面更廣(也為了準(zhǔn)確地模擬其他實(shí)現(xiàn)),方面必須維護(hù)狀態(tài)。也就是說,它應(yīng)當(dāng)為每個(gè)受監(jiān)視的流彈出一個(gè)惟一的進(jìn)度監(jiān)視器。AspectJ 提供了幾個(gè)處理這個(gè)問題的選擇。對(duì)于這個(gè)方面來說,最佳選擇可能是用 Map 維護(hù)每個(gè)對(duì)象的狀態(tài)存儲(chǔ)。這種技術(shù)在我實(shí)現(xiàn)的觀察者模式中會(huì)再次出現(xiàn),請(qǐng)注意觀察!(將特定狀態(tài)保存到對(duì)象的其他方法中,包括類型間聲明和 pertarget/perthis 方面,但是對(duì)這些概念的考慮超出了本文的范疇。) 要實(shí)現(xiàn)狀態(tài)存儲(chǔ),首先要聲明一個(gè) WeakHashMap,它用流作為鍵,把監(jiān)視器保存為值。可以使用 WeakHashMap,因?yàn)槿绻J褂弥胁辉傩枰I,那么 WeakHashMaps 不會(huì)阻止將它的鍵作為垃圾進(jìn)行收集。這個(gè)最佳實(shí)踐可以防止由于方面持有對(duì)不活動(dòng)對(duì)象的引用而造成的內(nèi)存泄漏。 然后 updateMonitor() 方法用 map 消極地初始化一個(gè)新的 IncrementMonitor。一旦該方法確定存在監(jiān)視器,就會(huì)用最新的進(jìn)度(read() 的返回值表示)來更新監(jiān)視器。在清單 4 中,可以看到實(shí)現(xiàn)消極初始化和進(jìn)度更新的代碼,以及 IncrementMonitor 的完整代碼: 清單 4. 每個(gè)流監(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); 最后,在已經(jīng)完全讀取完流的時(shí)候,方面需要釋放監(jiān)視器。如果在這個(gè)時(shí)候按照方面的思路去思考,您就會(huì)認(rèn)識(shí)到這是個(gè)機(jī)會(huì):InputStream 可以很方便地為將要通知的方面定義一個(gè) close() 方法,如下所示: before(InputStream is):call(public void InputStream+.close()& target(is)System.out.println(Discarding monitor.);perStreamMonitor.remove(is); 目前,練習(xí)已經(jīng)完成。但是,如果熟悉 InputStream 實(shí)現(xiàn)的話,那么可能會(huì)發(fā)現(xiàn),我故意遺漏了一些事。必須用與其他讀取方法不同的方法來處理 read() 方法 (沒有參數(shù)),因?yàn)樗姆祷刂挡皇亲x取的字節(jié)數(shù),而是讀取流中的下一個(gè)字節(jié)。隨著本文的示例代碼對(duì)方面進(jìn)行擴(kuò)展,可以處理這個(gè)限制,但是我建議您在參閱代碼之前,想想自己編寫方面時(shí)應(yīng)當(dāng)如何解決這個(gè)問題。AspectJ 修飾器的分析 像適配器模式一樣,修飾器模式的兩個(gè)實(shí)現(xiàn)的區(qū)別在于本地化。在 AspectJ 版本中,整個(gè)模式巧妙地處于一個(gè)方面中。(我排除了 IncrementMonitor 助手類,因?yàn)樗谀J街袥]有起到結(jié)構(gòu)化的作用。)在 Java 語言版本中,基本模式實(shí)現(xiàn) (不考慮客戶代碼)要求具有三個(gè)類。這有什么效果呢? 易于理解:因?yàn)?AspectJ 的切入點(diǎn)語言的威力,方面可以使用同一個(gè)通知影響多個(gè)操作。對(duì)比之下,修飾器類必須在每一個(gè)操作上重復(fù)該行為。Sun 實(shí)現(xiàn)的代碼行數(shù)是方面實(shí)現(xiàn)的兩倍多,其中部分是由于上述原因。ProgressMonitorInputStream 大約是 110 行,而 FilterInputStream 是 40 行(我放過了 InputStream,因?yàn)樗赡苁切揎椘髂J街械暮戏ǜ割悾?。?duì)比之下,MonitorFileReads 方面用了 53 行,而 IncrementMonitor 助手類用了 12 行。行的比率是 160 比 65,或者大約是 2.4 : 1。雖然 LOC 只是一個(gè)粗略的測(cè)量方法,但是一般來說,代碼越短就會(huì)越清晰。 而且,如果您熟悉 AOP,那么 AspectJ 解決方案不會(huì)給您留下正在運(yùn)行什么特殊事情的感覺。Java 語言解決方案要求幾個(gè)類小心地進(jìn)行協(xié)作,而 AspectJ 版本看起來就像是正在進(jìn)行大多數(shù)方面所做的工作:即通過通知將行為添加到一組連接點(diǎn)上。 最后,值得記住的是 AOP 的經(jīng)常遭批評(píng)的一個(gè)缺點(diǎn):再也不能通過閱讀源代碼了解模塊將做什么工作了。如果把修飾器應(yīng)用到對(duì)象上,并且沒有方面的幫助,那么在客戶代碼(不是包裝的位置) 或者在對(duì)象顯示附加行為的修改目標(biāo)中(FileInputStream),都沒有基于源代碼的線索。對(duì)比之下,如果在 AJDT 中檢查清單 2 的 GUI,那么可以看到在行 while (in.read(b) != -1) 上的友好注釋,這些注釋指明監(jiān)視器方面將影響讀取調(diào)用??梢詫?AspectJ 與它的開發(fā)環(huán)境結(jié)合,在這種情況,要比原先的實(shí)現(xiàn)提供更多的信息。 重用:由于修飾構(gòu)建到了語言中,幾乎所有方面都“重用”這個(gè)模式。更具體的重用可能是:讓監(jiān)視方面變得更抽象,允許子方面指定監(jiān)視操作的切入點(diǎn)。通過這種方式,差不多所有對(duì)象都能用監(jiān)視進(jìn)行修飾 不再需要進(jìn)行傳統(tǒng)實(shí)現(xiàn)要求的那些準(zhǔn)備工作。(如果想了解抽象方面,本文的 第 2 部分 將更詳細(xì)地解釋它們的用途。) 維護(hù):向?qū)ο筇砑有滦揎棽恍枰厥獾呐?。如果修飾目?biāo)變了(想像一下新的讀方法),那么(可能)必須對(duì)切入點(diǎn)進(jìn)行更新,以體現(xiàn)這種變化。必須更新切入點(diǎn)這一點(diǎn)有些累人,但是通過編寫能夠捕捉新操作的強(qiáng)壯切入點(diǎn),可以減輕這個(gè)負(fù)擔(dān)。(請(qǐng)參閱 參考資料,獲得關(guān)于強(qiáng)壯切入點(diǎn)的一個(gè) blog 的鏈接。)在任何情況下,更新切入點(diǎn)看起來都要比更新所有修飾器(就像在 Java 語言實(shí)現(xiàn)中進(jìn)行類似更改所要求的那樣)的麻煩少一些。 這里是另一個(gè)有趣的場(chǎng)景(在前面 Java 語言分析中提到過):監(jiān)視 所有的 文件讀取。使用 OO 修飾器,這就意味著讀取流的每個(gè)類都必須記得把自己包裝在 ProgressMonitorInputStream 中。對(duì)比之下,MonitorFileReads 方法會(huì)監(jiān)視任何輸入流上的讀取,只要它們是在 JComponent 的控制流程中發(fā)生的。由于 ProgressMonitor 只在操作進(jìn)行的時(shí)間比當(dāng)前閾值大的時(shí)候才彈出,所以這個(gè)方面可以透明地保證用戶在必須等候文件讀取時(shí)永遠(yuǎn)不會(huì)被打擾 程序員無需對(duì)此警惕。 組合:像競(jìng)爭性實(shí)現(xiàn)一樣,AspectJ 版本支持用很少的工作就可以透明地組合多個(gè)修飾器。 正如我前面提到過的,修飾器的主要秘密(透明地把行為添加到操作)包含在 AspectJ 語言中。AspectJ 實(shí)現(xiàn)的惟一挑戰(zhàn)是如何把方面的狀態(tài)(更新的進(jìn)度監(jiān)視器)與特定實(shí)例關(guān)聯(lián) 示例使用 map 實(shí)現(xiàn)這個(gè)關(guān)聯(lián)。處理關(guān)聯(lián)關(guān)系的需要使得修飾器作為模式保留在 AspectJ 中。有時(shí),當(dāng)修飾的機(jī)制已經(jīng)存在的時(shí)候,使用傳統(tǒng)的修飾器看起來更容易一些 特別是模式?jīng)]有入侵已修飾的類時(shí)。但是,如果修飾機(jī)制不存在,那么 AspectJ 實(shí)現(xiàn)的靈活性和簡單性就使其成為更好的選擇。 結(jié)束語 我希望對(duì)這兩個(gè)熟悉的模式的介紹有助于表現(xiàn)面向方面機(jī)制的實(shí)際應(yīng)用。隨著開發(fā)社區(qū)與不斷涌現(xiàn)的泛濫的范式斗爭,把新技術(shù)應(yīng)用到老問題上(已經(jīng)存在良好解決方案的問題)可能會(huì)有用 這樣的練習(xí)有助于用熟悉的方式評(píng)估新技術(shù)。 那么,迄今為止這方面進(jìn)行得如何呢?雖然不是一個(gè)金錘(golden hammer),但 AspectJ 已經(jīng)成功地保護(hù)了一些用來實(shí)現(xiàn)傳統(tǒng) OO 模式的固有優(yōu)勢(shì)。這些優(yōu)勢(shì)來自于 AspectJ 能夠更好地處理橫切關(guān)注點(diǎn)的能力。通過把模式的代碼搜集到單一方面中,AspectJ 使得通過閱讀代碼來理解模式變得更容易。因?yàn)槟J酱a沒有在非模式類(例如適配器和修飾器要求的包裝位置)中顯示,所以其他這些類也很容易理解。這種組合還使得擴(kuò)展和維護(hù)系統(tǒng)變得更加容易,甚至使到處重用模式也變得更加容易。 適配器和修飾器模式代表中等復(fù)雜的模式。在系列文章的 第 2 部分 中,我將研究是否可以將面向方面擴(kuò)展到更復(fù)雜的模式。具體地說,第 2 部分將研究觀察者模式,其中包括多個(gè)角色和動(dòng)態(tài)關(guān)系。在第 2 部分中,還將探索面向方面的重用 把模式或協(xié)議定義為抽象方面并用特定于應(yīng)用程序的方面來應(yīng)用它的能力。 1用AspectJ增強(qiáng)設(shè)計(jì)模式下在這篇文章的第1 部分中,我從面向方面的角度研究了兩個(gè)廣泛應(yīng)用的面向?qū)ο笤O(shè)計(jì)模式。在演示了適配器和修飾器模式在 Java 系統(tǒng)和 AspectJ 系統(tǒng)中的實(shí)現(xiàn)方式之后,我從代碼理解、重用、維護(hù)性和易于組合幾方面考慮了每種實(shí)現(xiàn)的效果。在兩種情況下,我發(fā)現(xiàn)橫切 Java 實(shí)現(xiàn)模塊性的模式在 AspectJ 實(shí)現(xiàn)中可以組合到單獨(dú)的一個(gè)方面中。理論上,這種相關(guān)代碼的協(xié)同定位可以使模式變得更易理解、更改和應(yīng)用。用這種方式看模式,就轉(zhuǎn)變對(duì) AOP 的一個(gè)常見批評(píng) 阻止開發(fā)人員通過閱讀代碼了解代碼的行為。在這篇文章的第 2 部分中,我將通過深入研究觀察者(Observer)模式,完成我對(duì) Java 語言的模式實(shí)現(xiàn)和 AspectJ 模式實(shí)現(xiàn)的比較。 我選擇把重點(diǎn)放在觀察者(Observer)模式上,因?yàn)樗荗O設(shè)計(jì)模式的皇后。該模式被人們廣泛應(yīng)用(特別是在 GUI 應(yīng)用程序中),并構(gòu)成了 MVC 架構(gòu)的關(guān)鍵部分。它處理復(fù)雜的問題,而在解決這類問題方面表現(xiàn)得相對(duì)較好。但是,從實(shí)現(xiàn)需要的努力和代碼理解的角度來說,它還是帶來了一些難以解決的難題。與修飾器或適配器模式不同(它們的參與者主要是為模式新創(chuàng)建的類),觀察者(Observer)模式要求您先侵入系統(tǒng)中現(xiàn)有的類,然后才能支持該模式 至少在 Java 語言中是這樣。 方面可以降低像觀察者(Observer)模式這種侵入性模式的負(fù)擔(dān),使得模式參與者更靈活,因?yàn)椴恍枰J酱a。而且,模式本身可以變成抽象的基本方面,允許開發(fā)人員通過導(dǎo)入和應(yīng)用它來實(shí)現(xiàn)重用,不必每次都要重新考慮模式。為了查看這些可能性如何發(fā)揮作用,我將繼續(xù)本文第一部分設(shè)置的格式。我將從示例問題開始,提供對(duì)觀察者(Observer)模式的通用描述。然后我將描述如何用 AspectJ 和 Java 語言實(shí)現(xiàn)觀察者(Observer)模式。在每個(gè)實(shí)現(xiàn)之后,我將討論是什么造成模式的橫切,模式的這個(gè)版本在理解、維護(hù)、重用和組合代碼方面有什么效果。 在繼續(xù)后面的討論之前,請(qǐng)單擊本頁頂部或底部的 代碼 圖標(biāo),下載本文的源代碼。 觀察者(Observer)模式 根據(jù) Portland Pattern Repository Wiki(請(qǐng)參閱 參考資料 一節(jié),獲得有關(guān)的細(xì)節(jié)),觀察者(Observer)模式的用途是定義對(duì)象之間的一對(duì)多依賴關(guān)系,因此,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),其所有依賴項(xiàng)都會(huì)得到通知,并自動(dòng)更新。這使得觀察者適用于所有類型的通知需要。請(qǐng)考慮以下情況: 關(guān)于本系列 AOPWork 系列是為具有一定面向方面的編程背景、并準(zhǔn)備擴(kuò)展或者加深其知識(shí)的開發(fā)人員準(zhǔn)備的。與大多數(shù)developerWorks 文章一樣,本系列具有很高實(shí)用性:從每一篇文章中學(xué)到的知識(shí)立刻就能使用得上。 所挑選的為這個(gè)系列撰稿的每一位作者,都在面向方面編程方面處于領(lǐng)導(dǎo)地位或者擁有這方面的專業(yè)知識(shí)。許多作者都是本系列中討論的項(xiàng)目或者工具的開發(fā)人員。每篇文章都經(jīng)過仔細(xì)審查,以確保所表達(dá)的觀點(diǎn)的公平和準(zhǔn)確。 關(guān)于文章的意見和問題,請(qǐng)直接與文章的作者聯(lián)系。如果對(duì)整個(gè)系列有意見,可以與本系列的組織者 Nicholas Lesiecki 聯(lián)系。更多關(guān)于 AOP 的背景知識(shí),請(qǐng)參閱 參考資料。 條形圖可以觀察它顯示的數(shù)據(jù)對(duì)象,以便在這些對(duì)象變化時(shí)對(duì)它們進(jìn)行重新繪制。 Acc
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 內(nèi)蒙古師范大學(xué)《高分子化學(xué)與物理實(shí)驗(yàn)》2023-2024學(xué)年第二學(xué)期期末試卷
- 幼兒游戲的教育作用
- 2024年刀軸式刨片機(jī)類項(xiàng)目資金籌措計(jì)劃書代可行性研究報(bào)告
- 疾病講課課件模板
- 2024年屏風(fēng)項(xiàng)目資金籌措計(jì)劃書代可行性研究報(bào)告
- 2025年陜西西安市惜才人才資源開發(fā)有限責(zé)任公司招聘筆試參考題庫含答案解析
- 2025年廣西梧州市交通投資集團(tuán)梧州公司招聘筆試參考題庫附帶答案詳解
- 2025年江蘇常州市舜溪旅游管理有限公司招聘筆試參考題庫附帶答案詳解
- 物聯(lián)網(wǎng)技術(shù)市場(chǎng)探秘-發(fā)掘千億市場(chǎng)洞察未來趨勢(shì)
- 軌道交通:綠色智慧未來-自動(dòng)駕駛與節(jié)能技術(shù)的融合
- 建設(shè)工程前期工作咨詢費(fèi)收費(fèi)計(jì)算表
- 中國糖尿病腎臟病防治指南(2021年版)
- 八年級(jí)物理下冊(cè)《實(shí)驗(yàn)題》專項(xiàng)練習(xí)題及答案(人教版)
- 中學(xué)生詩詞知識(shí)大賽備考題庫(500題)
- 陽光心態(tài)快樂職場(chǎng)
- 麻醉藥品專用賬冊(cè)
- 初、中、高級(jí)工程師申報(bào)培訓(xùn)課件
- 2023年山東大學(xué)考博英語完型填空和閱讀試題
- 俄羅斯地緣政治學(xué)
- GB/T 16180-2014勞動(dòng)能力鑒定職工工傷與職業(yè)病致殘等級(jí)
- GB/T 12513-2006鑲玻璃構(gòu)件耐火試驗(yàn)方法
評(píng)論
0/150
提交評(píng)論