高內聚, 低耦合

 首先要知道一個軟件是由多個子程序組裝而成,

而一個程序由多個模塊(方法)構成!
而內聚就是指程序內的各個模塊之間的關係緊密程度,
偶合就是各個外部程序(子程序)之間的關係緊密程度.
所以很易明白,爲什麼要高內聚?模塊之間的關係越緊密,出錯就越少!低偶合?子程序間的關係越複雜,就會產生更多的意想不到的錯誤!會給以後的維護工作帶來很多麻煩!
一個優秀軟件開發人員的必修課:高內聚
高內聚 .net 軟件工程軟件模式    
一個重要的模式:高內聚。
2. 高內聚(High Cohesion)
高內聚是另一個普遍用來評判軟件設計質量的標準。內聚,更爲專業的說法叫功能內聚,是對軟件系統中元素職責相關性和集中度的度量。如果元素具有高度相關的職責,除了這些職責內的任務,沒有其它過多的工作,那麼該元素就具有高內聚性,反之則爲低內聚性。高內聚要求軟件系統中的各個元素具有較高的協作性,因爲在我們在完成軟件需求中的一個功能,可能需要做各種事情,但是具有高內聚性的一個元素,只完成它職責內的事情,而把那些不在它職責內的事情拿去請求別人來完成。這就好像,如果我是一個項目經理,我的職責是監控和協調我的項目各個階段的工作。當我的項目進入需求分析階段,我會請求需求分析員來完成;當我的項目進入開發階段,我會請求軟件開發人員來完成;當我的項目需要測試的時候,我會請求測試人員。。。。。。如果我參與了開發,我就不是一個高內聚的元素,因爲開發不是我的職責。我們的項目爲什麼要高內聚呢?我覺得可以從可讀性、複用性、可維護性和易變更性四個方面來理解。
1.可讀性
一個人寫文章、講事情,條理清晰才能易於理解,這同樣發生在讀寫軟件代碼上。如果一堆代碼寫得一團亂麻,東一個跳轉西一個調用,讀它的人會感覺非常頭疼。這種事情也許一直在寫程序的你我都曾經有過經歷。如果一段程序條理非常清晰,每個類通過名稱或說明都能清楚明白它的意義,類的每個屬性、函數也都是易於理解的它所應當完成的任務和行爲,這段程序的可讀性必然提高。在軟件產業越來越密集,軟件產業中開發人員協作越來越緊密、分工越來越細的今天,軟件可讀性的要求相信也越來越爲人們所重視。
2.複用性
在軟件開發中,最低等級的複用是代碼拷貝,然後是函數的複用、對象的複用、組件的複用。軟件開發中最懶的人是最聰明的人,他們總是想到複用。在代碼編寫的時候突然發現某個功能是曾經實現過的功能,直接把它拷貝過來就ok了。如果這段代碼在同一個對象中,那麼就提出來寫一個函數到處調用就行了。如果不是在同一個對象中呢,就將其抽象成一個對象到處調用吧。如果不在一個項目中呢,那就做成組件給各個項目引用吧。代碼複用也使我們的代碼在複用的過程中不斷精化、不斷健壯、提高代碼質量。代碼的複用的確給我們的開發帶來了不少便利,但是一段代碼能否在各個需要的地方都能複用呢?這給我們的軟件開發質量提出了新的要求:好的代碼可以複用,不好的則不行。軟件中的一個對象如果能保證能完成自己職能範圍內的各項任務,同時又不去理會與自己職能無關的其它任務,那麼它就能夠保證功能的相對獨立性,也就可以脫離自己所處的環境而複用到其它環境中,這是一個具有內聚性的對象。
3.可維護性和易變更性
在前面《如何在struts+spring+hibernate的框架下構建低耦合高內聚的軟件》中我提到,我們現在的軟件是在不斷變更的,這種變更不僅來自於我們的客戶,更來自於我們的市場。如果我們的軟件通過變更能及時適應我們的市場需求,我們就可以在市場競爭中獲勝。如何能及時變更以適應我們的市場呢,就是通過調整軟件的結構,使我們每次的變更付出的代價最小,耗費的人力最小,這種變更才最快最經濟。高內聚的軟件,每個系統、模塊、類的任務都高度相關,就使每一次的變更涉及的範圍縮小到最小。比如評審表發生了變更,只會與評審表對象有關,我們不會去更改其它的對象。如果我們能做到這一點,我們的系統當然是可維護性好、易變更性好的系統。
那麼,我們如何做到高內聚呢?就拿前面我提到的評審項目舉例。我現在要爲“評審表”對象編寫一段填寫並保存評審表的代碼。評審表對象的職責是更新和查詢評審表的數據,但是在顯示一個要填寫的評審表的時候,我需要顯示該評審計劃的名稱、該評審計劃有哪些評審對象需要評審。現在我如何編寫顯示一個要填寫的評審表的代碼?我在評審表對象的這個相應的函數中編寫一段查詢評審計劃和評審對象的代碼嗎?假如你這樣做了,你的代碼就不是高內聚的,因爲查詢評審計劃和評審對象的數據不是它的職責。正確的方法應當去請求“評審計劃”對象和“評審對象”對象來完成這些工作,而“評審表”對象只是獲取其結果。
另外,如果一個對象要完成一個雖然在自己職責範圍內,但過程非常複雜的任務時,也應當將該任務分解成數個功能相對獨立的子函數來完成。我曾經看見一個朋友寫的數百行的一個函數,讓人讀起來非常費勁。同時這樣的函數中一些相對獨立的代碼,本可以複用到其它代碼中,也變成了不可能。所以我給大家的建議是,不要寫太長的函數,超過一百行就可以考慮將一些功能分解出去。
與“低耦合”一樣,高內聚也不是一個絕對,而是一個相對的指標,應當適當而不能過度。正如我們在現實生活中,如果在一個十來人的小公司,每個人的分工可能會粗一些,所分配的職責會廣一些雜一些,因爲其總體的任務少;而如果在一個一兩百人的大公司,每個人的分工會細一些,所分配的任務會更加專一些,因爲總體任務多,更需要專業化的分工來提高效率。軟件開發也是一樣,如果“評審計劃”對象完成的業務功能少,並且不復雜,它完全可以代理它的子表“評審對象”和“評審者”的管理。但是“評審計劃”對象需要完成的“對評審計劃表的管理”這個基本職責包含的業務功能繁多或者複雜,它就應當將“對評審對象表的管理”交給“評審對象”對象,將“對評審者表的管理”交給“評審者”對象。同樣,高內聚的可維護性好、易變更性好只能是一個相對的指標。如果一個變更的確是大範圍的變更,你永遠不可能通過內聚就不進行大範圍的變更了。同時內聚也是要付出代價的,所以你也不必要去爲了一個不太可能的變更去進行過度設計,應當掌握一個度。過度的內聚必將增加系統中元素之間的依賴,提高耦合度。所以“高內聚”與“低耦合”是矛盾的,必須權衡利弊,綜合地去處理。在李洋等人翻譯的《UML和模式應用》中,將內聚和耦合翻譯爲軟件工程中的陰與陽,是中國人對內聚和耦合的最佳解釋。
綜上所述,“高內聚”給軟件項目帶來的優點是:可讀性強、易維護和變更、支持低耦合、移植和重用性強。
一個優秀軟件開發人員的必修課:GRASP(2)低耦合
關鍵字: 設計模式       
我偶然在google或yahoo這樣的搜索引擎搜索GRASP發現,除了國外的網站,國內網站多介紹和討論GoF而很少介紹GRASP,即使這少量的文章也講解非常粗略。個人認爲作爲優秀的開發人員,理解GRASP比GoF更重要,故寫此文章。前面我在《(原創)一個優秀軟件開發人員的必修課:GRASP軟件開發模式淺析》中介紹了使用GRASP的目的,今天允許我調換一下順序,先從低耦合講起,因爲諸如創建者模式、信息專家模式的根本目的就是降低耦合。
1.    低耦合(Low Coupling)
“低耦合”這個詞相信大家已經耳熟能詳,我們在看spring的書籍、MVC的數據、設計模式的書籍,無處不提到“低耦合、高內聚”,它已經成爲軟件設計質量的標準之一。那麼什麼是低耦合?耦合就是對某元素與其它元素之間的連接、感知和依賴的量度。這裏所說的元素,即可以是功能、對象(類),也可以指系統、子系統、模塊。假如一個元素A去連接元素B,或者通過自己的方法可以感知B,或者當B不存在的時候就不能正常工作,那麼就說元素A與元素B耦合。耦合帶來的問題是,當元素B發生變更或不存在時,都將影響元素A的正常工作,影響系統的可維護性和易變更性。同時元素A只能工作於元素B存在的環境中,這也降低了元素A的可複用性。正因爲耦合的種種弊端,我們在軟件設計的時候努力追求“低耦合”。低耦合就是要求在我們的軟件系統中,某元素不要過度依賴於其它元素。請注意這裏的“過度”二字。系統中低耦合不能過度,比如說我們設計一個類可以不與JDK耦合,這可能嗎?除非你不是設計的Java程序。再比如我設計了一個類,它不與我的系統中的任何類發生耦合。如果有這樣一個類,那麼它必然是低內聚(關於內聚的問題我隨後討論)。耦合與內聚常常是一個矛盾的兩個方面。最佳的方案就是尋找一個合適的中間點。
哪些是耦合呢?
1.元素B是元素A的屬性,或者元素A引用了元素B的實例(這包括元素A調用的某個方法,其參數中包含元素B)。
2.元素A調用了元素B的方法。
3.元素A直接或間接成爲元素B的子類。
4.元素A是接口B的實現。
幸運的是,目前已經有大量的框架幫助我們降低我們系統的耦合度。比如,使用struts我們可以應用MVC模型,使頁面展現與業務邏輯分離,做到了頁面展現與業務邏輯的低耦合。當我們的頁面展現需要變更時,我們只需要修改我們的頁面,而不影響我們的業務邏輯;同樣,我們的業務邏輯需要變更的時候,我們只需要修改我們的java程序,與我們的頁面無關。使用spring我們運用IoC(反向控制),降低了業務邏輯中各個類的相互依賴。假如類A因爲需要功能F而調用類B,在通常的情況下類A需要引用類B,因而類A就依賴於類B了,也就是說當類B不存在的時候類A就無法使用了。使用了IoC,類A調用的僅僅是實現了功能F的接口的某個類,這個類可能是類B,也可能是另一個類C,由spring的配置文件來決定。這樣,類A就不再依賴於類B了,耦合度降低,重用性提高了。使用hibernate則是使我們的業務邏輯與數據持久化分離,也就是與將數據存儲到數據庫的操作分離。我們在業務邏輯中只需要將數據放到值對象中,然後交給hibernate,或者從hibernate那裏得到值對象。至於用Oracle、MySQL還是SQL Server,如何執行的操作,與我無關。
但是,作爲優秀的開發人員,僅僅依靠框架提供的降低軟件耦合的方法是遠遠不夠的。根據我的經驗,以下一些問題我們應當引起注意:
1)   根據可能的變化設計軟件
我們採用職責驅動設計,設計中盡力做到“低耦合、高內聚”的一個非常重要的前提是,我們的軟件是在不斷變化的。如果沒有變化我們當然就不用這麼費勁了;但是如果有變化,我們希望通過以上的設計,使我們在適應或者更改這樣的變化的時候,付出更小的代價。這裏提供了一個非常重要的信息是,我們努力降低耦合的是那些可能發生變更的地方,因爲降低耦合是有代價的,是以增加資源耗費和代碼複雜度爲代價的。如果系統中某些元素不太可能變更,或者降低耦合所付出的代價太大,我們當然就應當選擇耦合。有一次我試圖將我的表現層不依賴於struts,但發現這樣的嘗試代價太大而失去意義了。對於軟件可能變更的部分,我們應當努力去降低耦合,這就給我們提出一個要求是,在軟件設計的時候可以預判日後的變化。根據以往的經驗我認爲,一個軟件的業務邏輯和採用的技術框架往往是容易變化的2個方面。客戶需求變更是我們軟件設計必須考慮的問題。在RUP的開發過程中,爲什麼需要將分析設計的過程分爲分析模型和設計模型,愚以爲,從分析模型到設計模型的過程實際上是系統從滿足直接的客戶需求到優化系統結構、適應可預見的客戶需求變更的一個過程。這種客戶需求的變更不僅僅指對一個客戶需求的變更,更是指我們的軟件從適應一個客戶需求到適應更多客戶需求的過程。另一個方面,現在技術變更之快,EJB、hibernate、spring、ajax,一個一個的技術像走馬燈一樣從我們腦海中滑過,我們真不知道明天我在用什麼。在這樣的情況下,適應變化就是我們最佳的選擇。
2)   合理的職責劃分
合理的職責劃分,讓系統中的對象各司其職,不僅是提高內聚的要求,同時也可以有效地降低耦合。比如評審計劃BUS、評審表BUS、評審報告BUS都需要通過評審計劃DAO去查詢一些評審計劃的數據,如果它們都去直接調用評審計劃DAO(如圖A),則評審計劃BUS、評審表BUS、評審報告BUS三個對象都與評審計劃DAO耦合,評審計劃DAO一旦變更將與這三個對象都有關。在這個實例中,實際上評審計劃BUS是信息專家(關於信息專家模式我將在後面討論),評審表BUS和評審報告BUS如果需要獲得評審計劃的數據,應當向評審計劃BUS提出需求,由評審計劃BUS提供數據(如圖B)。經過這樣的調整,系統的耦合度就降低了。
3)   使用接口而不是繼承
通過對耦合的分析,我們不難發現,繼承就是一種耦合。如果子類A繼承了父類B,不論是直接或間接的繼承,子類A都必將依賴父類B。子類A必須使用在存在父類B的環境中,父類B不存在子類A就不能使用,這樣將影響子類A的可移植性。一旦父類B發生任何變更,更改或去掉一個函數名,或者改變一個函數的參數,都將導致子類A不得不變更,甚至重寫。假如父類B的子類數十上百個,甚至貫穿這個項目各個模塊,這樣的變更是災難性的。這種情況最典型的例子是我們現在使用hibernate和spring設計DAO對象的方式,具體的描述參見我寫的《如何在 struts + spring + hibernate的框架下構建低耦合高內聚的軟件結構》一文。
總之,“低耦合”給軟件項目帶來的優點是:易於變更、易於重用
發佈了54 篇原創文章 · 獲贊 4 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章