追求代碼質量: 謹防緊密耦合!使用依賴性倒置原則(Dependency Inversion Principle)解開緊密耦合的代碼 ![]() | ![]() |
![]() | 級別: 初級 Andrew Glover (aglover@stelligent.com), 總裁, Stelligent Incorporated 2007 年 6 月 14 日 我們知道緊密耦合的代碼不是個好現象,因此要在設計中盡量避免它 —— 但問題是如何才能避免緊密耦合呢。這個月,我們將學習如何識別一個系統(tǒng)是否有緊密耦合的問題,然后使用依賴性倒置原則解開這種緊密耦合。 在過去一年的時間中,我在“ 追求代碼質量 ”專欄撰寫了大量的文章。這些文章向大家介紹了許多可以改進代碼質量的工具和技巧。我已經向大家展示了如何應用代碼度量來監(jiān)控代碼庫的質量;如何使用 TestNG、FIT 和 Selenium 之類的測試框架來檢驗應用程序的功能;以及如何使用 XMLUnit 和 StrutsTestCase 之類的擴展框架(和一些功能強大的幫助工具,如 Cargo 和 DbUnit)來擴展測試框架的應用范圍。 雖然代碼度量和開發(fā)人員測試對于在整個開發(fā)過程中確保代碼質量非常重要(就像我經常所說的,要及時并經常進行測試),但是它們基本上只能對代碼質量做出反應。您通過測試和度量代碼來確定和量化代碼的質量,但是代碼本身都已經寫好了。不論您做出何等努力,都會受困于最初的設計。
當然,不同的方法所設計出來的軟件系統(tǒng)會有好有壞,良莠不齊。優(yōu)秀設計的關鍵因素之一就是注意保持系統(tǒng)的可維護性。粗劣設計的并可執(zhí)行的系統(tǒng)可能易于編寫,但是要對它們提供支持確實是一個挑戰(zhàn)。這些系統(tǒng)往往脆弱不堪,也就是說對系統(tǒng)中某個區(qū)域的修改將會影響到其它看上去毫不相干的區(qū)域,因此要對它們進行重構也相當的困難和耗時。向代碼庫中添加開發(fā)人員測試可以為我們提供工作的規(guī)劃,但是其進展本身仍然是一個艱苦和緩慢的過程。 我們可以通過重構來改進已經編寫好的代碼,但是通常來說在代碼已完成之后再進行改動花費巨大。而如果在一開始就把代碼編寫得 盡善盡美 會不會更加方便和輕松呢? 這個月,我將介紹一種非常主動的技巧,可以確保軟件系統(tǒng)的質量和可維護性。依賴性倒置原則 被證明是編寫可維護和可測試的高質量代碼的必要條件。依賴性倒置原則的基本思想就是對象應該依賴于抽象 而不是實現。
您可能至少聽說過面向對象編程中所使用的術語耦合(coupling)。耦合即應用程序中各組件(或各對象)間的相互關系。松散耦合的應用程序要比緊密耦合的應用程序更具模塊化。松散耦合應用程序中的組件依賴于各種接口和抽象類,而緊密耦合的系統(tǒng)則與之相反,其組件依賴于各種具體的類。在松散耦合的系統(tǒng)中,其組件是使用抽象而不是 實現來相互關連的。 如果有圖解的話,可以很輕松地理解緊密耦合的問題。舉例說明,圖 1 中的軟件系統(tǒng)的 GUI 與它的數據庫相耦合: 圖 1. 一個緊密耦合的系統(tǒng) ![]() GUI 對某個實現(而不是抽象)的依賴會對系統(tǒng)造成限制。在數據庫未啟動和運行的情況下 GUI 是無法執(zhí)行的。從功能的角度上看這種設計似乎并不是很糟糕 —— 畢竟,我們一直都是這樣編寫應用程序而且也沒有出什么問題 —— 但是測試就要另當別論了。
圖 1 中的系統(tǒng)使得隔離編程格外地困難,而這對測試和維護系統(tǒng)各個方面又十分必要。您將需要一個具有正確查找數據的活動數據庫來測試 GUI,和一個運行正常的 GUI 來測試數據訪問邏輯。您可以使用 TestNG-Abbot(現在的名稱為 FEST)來測試前端,但是這樣仍然無法告訴您任何有關數據庫功能的內容。 清單 1 展示了這種糟糕的耦合。GUI 的一個特定的按鈕定義了一個 清單 1. 把 ActionListener 定義為 GUI 中的一個按鈕
單擊 GUI 的按鈕組件后,直接從數據庫中檢索某個特定命令的狀態(tài),如清單 2 所示: 清單 2. GUI 通過 getOrderStatus 方法直接與數據庫通信
清單 2 中的代碼出現了問題,尤其是它通過一個硬編碼的 SQL 語句直接與一個硬編碼的數據庫進行通信。Yeeesh! 您能夠想像開發(fā)人員測試這種 GUI 和相關數據庫的挑戰(zhàn)嗎(順便說一下,測試本應該簡單得像測試一個 Web 頁面一樣)? 倘若對數據庫的任何改動都將 影響到 GUI,那么要考慮修改系統(tǒng)的話會使情況變得更糟。
現在在腦海中考慮一下使用依賴性倒置原則設計的相同的系統(tǒng)。如圖 2 所示,通過向應用程序添加兩個組件來解除應用程序中的耦合是可能的:這兩個組件分別是一個接口和一個實現: 圖 2. 一個松散耦合的系統(tǒng) ![]() 在圖 2 所示的應用程序中,GUI 依賴于一個抽象 —— 一個數據訪問對象或 DAO。DAO 的執(zhí)行直接依賴于數據庫,但是 GUI 本身并沒有陷入其中。以 DAO 的形式添加一個抽象可以從 GUI 實現將數據庫實現解耦。一個接口會替代數據庫與 GUI 代碼相耦合。清單 3 顯示了該接口。 清單 3. WidgetDAO 是一個能幫助解耦架構的抽象
GUI 的 清單 4. GUI 依賴于抽象,而不是數據庫
對 GUI 完全隱藏了這個接口的實際實現,因為它是通過一個工廠來請求實現類型的,如清單 5 所示: 清單 5. 對 GUI 隱藏了 WidgetDAO 實現
注意,清單 5 中的 GUI 中的代碼只引用接口類型 —— GUI 中的任何地方都沒有使用(或導入)接口的實現。這種對實現細節(jié)的抽象是靈活性的關鍵:它使您能夠更換實現類型,而完全不會影響到 GUI。 還要注意,清單 5 中的 清單 6. 工廠對 GUI 隱藏了實現細節(jié)
使 GUI 引用對某個接口類型的數據檢索可以為創(chuàng)建不同的實現提供靈活性。在這種情況下,部件信息保存在數據庫中,因此可以創(chuàng)建一個 清單 7. WidgetDAO 類型的任務
注意,實現代碼并未包含在這些例子中。這些代碼并不重要,真正有價值的是原理。您不應該關心
因為 GUI 現在依賴于某個抽象并且通過一個工廠來獲得該抽象的實現,所以我們可以輕易地創(chuàng)建一個沒有與數據庫或者文件系統(tǒng)相耦合的模仿類。模仿類用于分離 GUI,如清單 8 所示: 清單 8. 分離變得簡單
添加一個模仿類是設計可維護性的系統(tǒng)的最后一個步驟。把 GUI 與 數據庫分離開來意味著我們可以測試 GUI 而無需關心特定的數據。我們還可以測試數據訪問邏輯而無需關心 GUI。
您可能沒有過多地考慮這些,但是您如今所設計和構建的應用程序使用壽命可能非常長久。您將繼續(xù)開發(fā)其它的項目,或者在其它的公司工作,但是您的代碼(如 COBOL)將會留下來,甚至有可能使用幾十年。 開發(fā)人員所贊同的一點是:編寫良好的代碼易于維護,依賴性倒置原則是進行可維護性設計的可靠方法。依賴性倒置注重依賴于抽象(而非實現),這樣可以在同一個代碼庫中創(chuàng)建大量的靈活性。借助一個 DAO 來應用這個技巧,就如您這個月所看到的,不僅可以確保您能夠在需要的時候修改代碼庫,還可以使其它的開發(fā)人員修改代碼庫。
|