如何衡量軟件設(shè)計(jì)質(zhì)量1
首要的標(biāo)準(zhǔn)
滿足軟件的功能需求
滿足軟件功能需求的設(shè)計(jì)并不一定就是好的設(shè)計(jì)。
好的設(shè)計(jì)
可讀性:軟件的設(shè)計(jì)文檔是否輕易被其他程序員理解。可讀性差的設(shè)計(jì)會(huì)給大型軟件的開發(fā)和維護(hù)過程帶來嚴(yán)重的危害。
可復(fù)用性:軟件系統(tǒng)的架構(gòu)、類、組件等單元能否很容易被本項(xiàng)目的其它部分或者其它項(xiàng)目復(fù)用。
可擴(kuò)展性:軟件面對(duì)需求變化時(shí),功能或性能擴(kuò)展的難易程度。
可維護(hù)性:軟件維護(hù)(主要是指軟件錯(cuò)誤的修改、遺漏功能的添加等)的難易程度。
上面四個(gè)標(biāo)準(zhǔn)太抽象了,無法考量
內(nèi)聚度
耦合度
什么是內(nèi)聚度
定義:表示一個(gè)應(yīng)用程序的單個(gè)單元所負(fù)責(zé)的任務(wù)數(shù)量和多樣性。內(nèi)聚與單個(gè)類或者單個(gè)方法單元相關(guān)。
好的軟件設(shè)計(jì)應(yīng)該做到高內(nèi)聚。
理想狀態(tài)下,一個(gè)代碼單元應(yīng)該負(fù)責(zé)一個(gè)內(nèi)聚的任務(wù),也就是說一個(gè)任務(wù)可以看作是一個(gè)邏輯單元。一個(gè)方法應(yīng)該實(shí)現(xiàn)一個(gè)邏輯操作,一個(gè)類應(yīng)該代表一種類型的實(shí)體。
內(nèi)聚原則背后的主要原因是重用。遵循該規(guī)則的另一個(gè)優(yōu)點(diǎn)是,當(dāng)一個(gè)應(yīng)用程序的某些方面需要做出改變時(shí),我們能夠在相同單元中找到所有相關(guān)的部分。
如果一個(gè)系統(tǒng)單元只負(fù)責(zé)一件事情,就說明這個(gè)系統(tǒng)單元有很高的內(nèi)聚度;如果一個(gè)系統(tǒng)單元負(fù)責(zé)了很多不相關(guān)的事情,則說明這個(gè)系統(tǒng)單元是內(nèi)聚度很低。
內(nèi)聚度的簡單判斷方法
如果一個(gè)方法可以用簡單的“動(dòng)詞+名詞”的形式來命名,或者如果一個(gè)類可以用準(zhǔn)確的名詞來命名,那么這樣的類或者方法就是內(nèi)聚度較高的系統(tǒng)單元;反之,如果類或者方法的名字必須包含“和”、“或”等字樣才能準(zhǔn)確反映其功能特性的話,這些類或方法的內(nèi)聚度就一定不高。
什么是耦合度
耦合度表示類之間關(guān)系的緊密程度。
耦合度決定了變更一個(gè)應(yīng)用程序的容易程度。在緊密耦合的類結(jié)構(gòu)中,更改一個(gè)類會(huì)導(dǎo)致其它的類也隨之需要做出修改。顯然,這是我們在類設(shè)計(jì)時(shí)應(yīng)該避免的,因?yàn)槲⑿〉男薷臅?huì)迅速波動(dòng)影響到整個(gè)應(yīng)用程序。此外,找到需要修改的所有的地方是必須的,實(shí)際上就使得修改變得困難并且耗費(fèi)時(shí)間。而在松散耦合的系統(tǒng)中,我們可以更改一個(gè)類,不需要修改其它類,而應(yīng)用程序仍然能夠正常工作。
設(shè)計(jì)原則
“高內(nèi)聚、低耦合”是所有優(yōu)秀軟件的共同特征。
如何做到? 在設(shè)計(jì)時(shí)遵循一定的設(shè)計(jì)原則。
一)單一職責(zé)
Single Responsibility PRinciple,SRP
定義:所有的對(duì)象都應(yīng)該有單一的職責(zé),它提供的所有的服務(wù)也都僅圍繞著這個(gè)職責(zé)。
換句話說就是:一個(gè)類而言,應(yīng)該僅有一個(gè)引起它變化的原因,永遠(yuǎn)不要讓一個(gè)類存在多個(gè)改變的理由。
單一職責(zé)的理解
要理解單一職責(zé)原則,首先我們要理解什么是類的職責(zé)。類的職責(zé)是由該類的對(duì)象在系統(tǒng)中的角色所決定的。舉例來講,教學(xué)管理系統(tǒng)中,老師就代表著一種角色,這個(gè)角色決定老師的職責(zé)就是教學(xué)。而要完成教學(xué)的職責(zé),老師需要講課、批改作業(yè),而講課、批改作業(yè)的行為就相當(dāng)于我們在程序中類的方法,類的方法和屬性就是為了完成這個(gè)職責(zé)而設(shè)置的。
類的單一職責(zé)是說一個(gè)類應(yīng)該只做一件事情。如果類中某個(gè)方法或?qū)傩耘c它所要完成的職責(zé)無關(guān),或是為了完成另外的職責(zé),那么這樣的設(shè)計(jì)就不符合類的單一職責(zé)原則。而這樣的設(shè)計(jì)的缺點(diǎn)是降低了類的內(nèi)聚性,增強(qiáng)了類的耦合性。由此帶來的問題是當(dāng)我們使用這個(gè)類時(shí),會(huì)把原本不需要的功能也帶到了代碼中,從而造成冗余代碼或代碼的浪費(fèi)。
單一職責(zé)原則的思考
單一職責(zé)原則提出了對(duì)對(duì)象職責(zé)的一種理想期望。對(duì)象不應(yīng)該承擔(dān)太多職責(zé),正如人不應(yīng)該一心分為二用。唯有專注,才能保證對(duì)象的高內(nèi)聚;唯有單一,才能保證對(duì)象的細(xì)粒度。對(duì)象的高內(nèi)聚與細(xì)粒度有利于對(duì)象的重用。一個(gè)龐大的對(duì)象承擔(dān)了太多的職責(zé),當(dāng)客戶端需要該對(duì)象的某一個(gè)職責(zé)時(shí),就不得不將所有的職責(zé)都包含進(jìn)來,從而造成冗余代碼或代碼的浪費(fèi)。
單一職責(zé)原則還有利于對(duì)象的穩(wěn)定。對(duì)象的職責(zé)總是要提供給其他對(duì)象調(diào)用,從而形成對(duì)象與對(duì)象的協(xié)作,由此產(chǎn)生對(duì)象之間的依賴關(guān)系。對(duì)象的職責(zé)越少,則對(duì)象之間的依賴關(guān)系就越少,耦合度減弱,受其他對(duì)象的約束與牽制就越少,從而保證了系統(tǒng)的可擴(kuò)展性。
單一職責(zé)原則并不是極端地要求我們只能為對(duì)象定義一個(gè)職責(zé),而是利用極端的表述方式重點(diǎn)強(qiáng)調(diào):在定義對(duì)象職責(zé)時(shí),必須考慮職責(zé)與對(duì)象之間的所屬關(guān)系。職責(zé)必須恰如其分地表現(xiàn)對(duì)象的行為,而不至于破壞和諧與平衡的美感,甚至格格不入。換言之,該原則描述的單一職責(zé)指的是公開在外的與該對(duì)象緊密相關(guān)的一組職責(zé)。
二)開閉原則
開閉原則(Open-Close Principle,簡稱OCP)是指一個(gè)軟件實(shí)體(類、模塊、方法等)應(yīng)該對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉。
遵循開閉原則設(shè)計(jì)出來的模塊具有兩個(gè)基本特征:
對(duì)于擴(kuò)展是開放的(Open for extension):模塊的行為可以擴(kuò)展,當(dāng)應(yīng)用的需求改變時(shí),可以對(duì)模塊進(jìn)行擴(kuò)展,以滿足新的需求。
對(duì)于更改是封閉的(Closed for modification):對(duì)模塊行為擴(kuò)展時(shí),不必改動(dòng)模塊的源代碼或二進(jìn)制代碼。
看起來相互矛盾
如何實(shí)現(xiàn)開閉原則?
關(guān)鍵在于抽象化
到底該抽象化什么?或者到底該將什么東西抽象為抽象類或者接口?
抽象化分為兩種情況:
針對(duì)多個(gè)領(lǐng)域類的抽象化
針對(duì)單個(gè)領(lǐng)域類的抽象化
多個(gè)領(lǐng)域類的抽象化
一組對(duì)象的共同行為抽象到抽象類或者接口中,而將不同行為的實(shí)現(xiàn)封裝在子類或者實(shí)現(xiàn)類中。接口或抽象類是不能實(shí)例化的,因此對(duì)修改就是關(guān)閉的;而添加新功能只要實(shí)現(xiàn)接口或者繼承抽象類,從而實(shí)現(xiàn)對(duì)擴(kuò)展開放。
使用抽象類。在設(shè)計(jì)類時(shí),對(duì)于擁有共同功能的相似類進(jìn)行抽象化處理,將公用的功能部分放到抽象類中,而將不同的行為封裝在子類中。這樣,在需要對(duì)系統(tǒng)進(jìn)行功能擴(kuò)展時(shí),只需要依據(jù)抽象類實(shí)現(xiàn)新的子類即可。在擴(kuò)展子類時(shí),不僅可以擁有抽象類的共有屬性和共有方法,還可以擁有自定義的屬性和方法。
使用接口。與抽象類不同,接口只定義實(shí)現(xiàn)類應(yīng)該實(shí)現(xiàn)的接口方法,而不實(shí)現(xiàn)公有的功能。在現(xiàn)在大多數(shù)的軟件開發(fā)中,都會(huì)為實(shí)現(xiàn)類定義接口,這樣在擴(kuò)展子類時(shí)必須實(shí)現(xiàn)該接口。如果要改換原有的實(shí)現(xiàn),只需要改換一個(gè)實(shí)現(xiàn)類即可。
單個(gè)領(lǐng)域類
將單個(gè)領(lǐng)域類中可能會(huì)發(fā)生變化的行為進(jìn)行封裝,也就是找出類中可能需要變化之處,把它們封裝成抽象類或者接口,從而將變化點(diǎn)與不需要變化的代碼分離。
如果每次新的需求一來,都會(huì)使一個(gè)領(lǐng)域類的某個(gè)行為的代碼發(fā)生變化,那么我們就可以確定,這部分的代碼需要被抽象出來,和其它穩(wěn)定的代碼有所區(qū)分。把會(huì)變化的部分取出并封裝出抽象類或接口,以便以后可以輕易地改動(dòng)或擴(kuò)充此部分,而不會(huì)影響不需要變化的其它部分。封裝變化點(diǎn)的好處在于,將類中經(jīng)常變化的部分和穩(wěn)定的部分隔離,有助于增加復(fù)用性,并降低系統(tǒng)耦合度。
開閉原則是核心
開閉原則是面向?qū)ο笤O(shè)計(jì)的核心所在。遵循這個(gè)原則可以帶來靈活性、可重用性和可維護(hù)性。其它設(shè)計(jì)原則(里氏替換原則、依賴倒轉(zhuǎn)原則、組合/聚合復(fù)用原則、迪米特法則、接口隔離原則)是實(shí)現(xiàn)開閉原則的手段和工具。
三)里氏替換原則
里氏替換原則(The Liskov Substitution Principle,LSP)的定義:在一個(gè)軟件系統(tǒng)中,子類應(yīng)該能夠完全替換任何父類能夠出現(xiàn)的地方,并且經(jīng)過替換后,不會(huì)讓調(diào)用父類的客戶程序從行為上有任何改變。
里氏替換原則的意義
里氏替換原則是使代碼符合開閉原則的一個(gè)重要的保證,同時(shí),它體現(xiàn)了:
類的繼承原則:里氏替換原則常用來檢查兩個(gè)類是否為繼承關(guān)系。在符合里氏替換原則的繼承關(guān)系中,使用父類代碼的地方,用子類代碼替換后,能夠正確的執(zhí)行動(dòng)作處理。換句話說,如果子類替換了父類后,不能夠正確執(zhí)行動(dòng)作,那么他們的繼承關(guān)系就是不正確的,應(yīng)該重新設(shè)計(jì)它們之間的關(guān)系。
動(dòng)作正確性保證:里氏替換原則對(duì)子類進(jìn)行了約束,所以在為已存在的類進(jìn)行擴(kuò)展,來創(chuàng)建一個(gè)新的子類時(shí),符合里氏替換原則的擴(kuò)展不會(huì)給已有的系統(tǒng)引入新的錯(cuò)誤。
里氏代換原則給我們的啟示
類的繼承原則:如果一個(gè)繼承類的對(duì)象可能會(huì)在基類出現(xiàn)的地方出現(xiàn)運(yùn)行錯(cuò)誤,則該子類不應(yīng)該從該基類繼承,或者說,應(yīng)該重新設(shè)計(jì)它們之間的關(guān)系。
動(dòng)作正確性保證:符合里氏代換原則的類擴(kuò)展不會(huì)給已有的系統(tǒng)引入新的錯(cuò)誤。
四)依賴倒轉(zhuǎn)原則
依賴倒轉(zhuǎn)原則(Dependency Inversion Principle,簡稱DIP)是指將兩個(gè)模塊之間的依賴關(guān)系倒置為依賴抽象類或接口。具體有兩層含義:
高層模塊不應(yīng)該依賴于低層模塊,二者都應(yīng)該依賴于抽象;
抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)(子類,實(shí)現(xiàn)類)應(yīng)該依賴于抽象。
依賴倒轉(zhuǎn)原則給我們的啟示
要針對(duì)接口編程,不要針對(duì)實(shí)現(xiàn)編程。(Program to an interface. not an implementation)
五)組合/聚合復(fù)用原則
組合/聚合復(fù)用原則(Composite/Aggregation Reuse Principle,CARP)是指要盡量使用組合/聚合而非繼承來達(dá)到復(fù)用目的。另一種解釋是在一個(gè)新的對(duì)象中使用一些已有的對(duì)象,使之成為新對(duì)象的一部分;新的對(duì)象通過向這些對(duì)象委托功能達(dá)到復(fù)用這些對(duì)象的目的。
在面向?qū)ο蟮脑O(shè)計(jì)中,有兩種方法可以實(shí)現(xiàn)對(duì)已有對(duì)象重用的目的,即通過組合/聚合,或者通過繼承。那么,這兩種不同的復(fù)用方式在可維護(hù)性方面有什么區(qū)別呢?
1)組合/聚合復(fù)用
2)繼承復(fù)用
組合/聚合復(fù)用的好處:
1)新對(duì)象存取成分對(duì)象的唯一方法是通過成分對(duì)象的接口。
2)這種對(duì)象的復(fù)用是黑箱復(fù)用,因?yàn)槌煞謱?duì)象的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)對(duì)于新的對(duì)象是看不見的。
3)這種復(fù)用所用的依賴更少。
4)新對(duì)象可以在運(yùn)行的時(shí)候動(dòng)態(tài)的引用于成分對(duì)象類型相同的對(duì)象。
繼承復(fù)用的優(yōu)點(diǎn):
1)新的實(shí)現(xiàn)較為容易,因?yàn)槌惖拇蟛糠止δ芸梢酝ㄟ^繼承關(guān)系自動(dòng)進(jìn)入子類。
2)修改或者擴(kuò)展繼承而來的實(shí)現(xiàn)比較容易。
繼承復(fù)用的缺點(diǎn):
1)繼承復(fù)用破壞包裝。將超類的實(shí)現(xiàn)細(xì)節(jié)暴露給子類。超類的內(nèi)部細(xì)節(jié)常常對(duì)子類是透明的,白箱復(fù)用。
2)超類的實(shí)現(xiàn)發(fā)生了改變,子類的實(shí)現(xiàn)也不得不改變。
3)超類繼承而來的是靜態(tài)的,不可能在運(yùn)行時(shí)間內(nèi)發(fā)生改變。因此沒有足夠的靈活性。
六)接口隔離原則
接口隔離原則(Interface Segregation Principle,簡稱ISP)是指客戶不應(yīng)該依賴它們用不到的方法,只給每個(gè)客戶它所需要的接口。換句話說,就是不能強(qiáng)迫用戶去依賴那些他們不使用的接口。
接口隔離原則兩層意思(1)
接口的設(shè)計(jì)原則:接口的設(shè)計(jì)應(yīng)該遵循最小接口原則,不要把用戶不使用的方法塞進(jìn)同一個(gè)接口里。如果一個(gè)接口的方法沒有被使用到,則說明該接口過胖,應(yīng)該將其分割成幾個(gè)功能專一的接口,使用多個(gè)專門的接口比使用單一的總接口要好。
示例:手機(jī)
接口隔離原則兩層意思(2)
接口的繼承原則:如果一個(gè)接口A繼承另一個(gè)接口B,則接口A相當(dāng)于繼承了接口B的方法,那么繼承了接口B后的接口A也應(yīng)該遵循上述原則:不應(yīng)該包含用戶不使用的方法。反之,則說明接口A被B給污染了,應(yīng)該重新設(shè)計(jì)它們的關(guān)系。
示例:門
七)迪米特法則(解耦)
迪米特法則(Law of Demeter,簡稱LOD),又稱為“最少知識(shí)原則”,它的定義為:一個(gè)軟件實(shí)體應(yīng)當(dāng)盡可能少的與其他實(shí)體發(fā)生相互作用。
媽媽說:不要與陌生人說話!
示例:GUI的設(shè)計(jì)
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注