前言
「殺死一個程序員不須要用槍,改三次需求就能夠了」,雖然這是一句話笑話,但也從側面描述出了軟件的可維護性重要性程序員
知名軟件大師Robert C.Martin認爲一個可維護性(Maintainability)較低的軟件設計,一般因爲如下4個緣由形成算法
1)過於僵化(Rigidity):設計難以修改數據庫
2)過於脆弱(Fragility):設計易遭到破壞(須要修改的時候,容易牽一髮而動全身,不應受到影響的代碼也被迫的破壞掉)編程
3)牢固性(Immobility):複用率低(當想使用一些功能時會發現裏面的某些代碼不是他想要的,想把這些代碼去掉時,發現沒辦法去掉,緣由是代碼耦合度過高了)設計模式
4)粘度太高(Viscosity):難以作正確事情(維護的過程當中想進行修改某些代碼,可是發現沒有辦法進行修改,緣由就是粘度過高)服務器
軟件工程和建模大師PeterCoad認爲,一個好的系統設計應該具有以下三個特性:架構
1)可擴展性(Extendibility)ide
2)靈活性(Flexibility)函數
3)可插入性(Pluggability)工具
面向對象設計原則和設計模式也是對系統進行合理重構,重構是在不改變軟件現有功能的基礎上,經過調整代碼改善軟件的質量、性能,使其程序的設計模式和架構更趨合理性,提升軟件的擴展性和維護性。
七種原則並非孤立存在的,他們相互依賴,相互補充。
1.單一職責原則(重要性4顆星)
類的職責要單一,不能將太多的職責放在一個類中
一個對象應該只包含單一的職責,而且該職責被完整地封裝在一個類中(另外一種定義是就一個類而言,應該僅有一個引發它變化的緣由。)
1)一個類承擔的職責越多,它被複用的可能性越小,並且若是一個類承擔的職責過多,就至關於將這些職責耦合在一塊兒,當其中一個職責變化時,可能會影響其餘職責的運做。
2)類的職責主要包括倆個方面:數據職責和行爲職責,數據職責經過其屬性來體現,而行爲職責經過其方法來體現。(注重行爲職責)
3)單一職責原則是實現高內聚,低耦合的指導方針,在不少代碼重構手段中都能找到它的存在,它是最簡單又最難運用的原則,須要實際人員發現類的不一樣職責將其分離,而發現類的多重職責須要設計人員具備較強的分析設計能力和相關重構經驗。
登陸功能經過以下登陸類(Login)實現:
init方法是對登陸頁面進行初始化的方法
display方法是顯示登陸界面
validate方法是對登陸的帳戶和密碼的合法性進行驗證(頁面上的語法驗證)
getConnertion和findUser方法對帳戶和密碼的合法性進行具體驗證
main方法是程序的入口
前三個是頁面的方法,後面倆個是業務上的方法,最後一個是程序的入口,幾個方法都屬於不一樣方面的,說明承擔的職責有些多。一個類應該只有一個點變化點,應該各司其職,不能由於某個緣由而致使類發生改變,反之就代表這個類承擔的職責太多了。打個比方說若是頁面發生改變,init方法就須要進行更改;再打個比方說驗證規則發生變化,validate方法須要更改等;這些都是變化點。因此說Login是不符合單一職責原則的(因此說,咱們能夠經過變化點來判斷是否符合單一職責原則)。
將Login進行分解爲MainClass(負責程序的入口)LoginForm(界面視圖),UserDAO(和用戶對象打交道的類),DBUtil(負責和數據庫進行鏈接的類)這樣的四個類。咱們能夠看出這四個是不一樣的變化點,它們之間都是無關的,大打個比方說,若是界面須要改變,咱們只須要將LoginForm類裏面的方法,不須要更改其餘類的方法,所以這樣的分解是真正符合單一職責原則。
若是遵循單一職責原則的思想的話,咱們就能夠將一個複雜(或承擔過多職責)的類按照業務邏輯這種思想把它進行分解,拆分紅一個個高內聚低耦合的這樣的一個類,最終目的是提升軟件(或者這個類)的可維護性(或者可複用性),因此說這個思想是很是簡單,可是咱們在作的時候倒是很難的。從這裏咱們應該也能看出來結構是很是重要的。因此咱們在平時的練習中有意識的往這方面靠近,必定要認真的遵循這個原則。
2.開閉原則(重要性5顆星)
軟件實現對擴展是開放的,但對修改是關閉的,即在不修改一個軟件實體的基礎上去擴展其功能
也就是說在設計一個模塊的時候,應對使這個模塊能夠在不被修改的前提下被擴展,即實如今不修改源代碼的狀況下改變這個模塊的行爲。
1)開閉原則由Bertand Meyer於1988年提出的,它是面向對象設計中最重要的原則之一。
2)在開閉原則的定義中,軟件實體能夠指一個軟件模塊、一個由多個類組成的局部結構或一個獨立的類。
3)抽象化是開閉原則的關鍵。
抽象是指一個類或者多個類裏面共性的東西抽取出來。
抽象的最大好處就是穩定的、可靠的‘不容易發生改變的。
4)開閉原則還能夠經過一個更加具體的「對可變性封裝原則「來描述,對可變性原則要求找到系統的可變因素並將其封裝起來。
某圖形界面系統提供了各類不一樣形狀的按鈕,客戶端代碼可針對這些按鈕進行編程,用戶可能會改變需求要求使用不一樣的按鈕,原始設計方案如圖所示:
如今客戶想作一個登錄界面,想作一個圓形的按鈕,在LoginForm界面裏包含一個CircleButton類,在類裏面提供一個display方法。可是過了一段時間,客戶想把圓形按鈕編程矩形按鈕,只能把button接口改變成圓形接口。若是按照咱們平時的思想來講是合情合理的,可是若是按照開閉原則來講,它是違背了開閉原則的設計,並非好的設計。
這裏增長了一個AbstactButton的抽象類,下面進行倆個類的實現。LoginFrom類中的button類型發生改變,有以前的具體類型變爲AbstarctButton類。能夠經過多態進行轉型。印證了抽象化是實現開閉原則的關鍵。
開閉原則思想就是在不穩定的類中間添加一個穩定的抽象類,而後把本身須要的點從具體的類中提取出來放到第三方類中,加強代碼的穩定性。因此說抽象方法時開閉原則的關鍵。
開閉原則是衡量一個代碼好壞的標準。
3.里氏替換原則(重要性4顆星)
在軟件系統中,一個能夠接受基類對象的地方必然能夠接受一個子類對象
若是對每個類型爲S的對象o1,都有類型爲T的對象o2,使得以T定義的因此程序P在全部的對象o1都替換成o2時,程序P的行爲都沒有變化,那麼類型S是類型T的子類型。
(全部用於基類的地方必須能透明地使用其子類的對象)
1)里氏替換原則由2008年圖靈獎得主、美國第一位計算機科學女博士、麻省理工學院教授Barbara Liskov和卡內基。梅隆大學Jeannette Wing教授於1994年提出。
2)里氏替換原則能夠通俗表述爲:在軟件中若是可以使用基類對象,那麼必定可以使用其子類對象。(注)把基類都替換成它的子類,程序將不會產生任何錯誤和異常,反過來則不成立,若是一個軟件實體使用的是一個子類的話,那麼它不必定可以使用基類。
3)里氏替換原則是實時開閉原則的重要方式之一,因爲使用基類對象的地方均可以使用子類對象,所以在程序中儘可能使用基類類型來對對象進行定義,而在運行時在肯定其子類類型,用子類對象來替換父類對象。
某系統須要實現對重要數據(如用戶密碼)的加密處理,在數據操做類(DataOperator)中須要調用加密類中定義的加密算法,系統提供了倆個不一樣的加密類,CipherA和CipherB,他們實現不一樣的加密方法,在DataOperator中能夠選擇其中的一個實現加密操做。如圖所示:
Client是客戶端(main方法)
DataOperator數據操做類
CipherA和CipherB倆個加密類,以不一樣的方式加密
若是須要更換一個加密算法類或者增長並使用一個新的加密算法類,如將CipherA改成CiherB,則須要修改客戶類Client和數據操做類DataOperator的源代碼,違背了開閉原則。
緣由是DataOperator裏面的方法直指倆個加密類,關係太緊密,耦合度過高。
1.將原先無關係的CipherA和CipherB倆個類變成繼承起來(由於都是給明文,獲得密文。做用是同樣的,方法不一樣而已)將CipherA當作父類,把CipherB當作子類。
2.將DataOperator類裏面的變量變成CipherA這個基類。
3.把多餘的set方法去掉,只要父類的。裏面傳的變量也是基類。
咱們把客戶端想使用的算法放置到客戶端外面的xml文件裏,若是想換就改配置文件就能夠了,若是擴展直接添加子類。
若是代碼中有大量的if-else或者case這種選擇結構的話,說明代碼須要使用里氏替換原則進行代碼優化,將各類不一樣的狀況進行抽象化,提取公共的東西建立一個父類,讓子類繼承,繼承完後須要實現不一樣的功能時,就須要使用@Override重寫方法(多態的形式實現)
若是程序遵循里氏替換原則,繼承就能成爲下降複雜的一個強大工具,由於它能讓程序員關注於對象的通常特性而沒必要擔憂細節。若是程序員必需要不斷地思考不一樣派生類的實如今語義上的差距,繼承就只會增長複雜度了。
4.依賴倒置原則(重要性5顆星)
要針對抽象層編程,而不是針對具體類編程
高層模塊不該該依賴底層模塊,它們都應該依賴抽象。
抽象不該該依賴於細節,細節應該依賴抽象。
要針對接口編程,不要針對實現編程。
1)結構良好的面向對象架構都具備清晰的層次定義,每一個層次經過一個定義良好的,受控的接口向外提供一組內聚的服務。
高層Policy Layer->低層Mechanism Layer->低層UtilityLayer,看起來彷佛正常(若是底層裏面的屬性或方法的名字發生改變,是否是高層的也須要發生改變,因此說是有問題的。代碼脆弱)
2)依賴倒置原則是Robert C.Martin在1996年爲《C++Reporter》所寫的專欄Engineering Notebook的第三篇,後來加入到他在2002年出版的經典《Agile Software Development,Princiles,Patterns,and Practices》中
3)簡單來講,依賴倒置原則就是指:代碼要依賴於抽象的類,而不要依賴於具體的類;要針對接口或抽象類編程,而不是針對具體類編程。
依賴(耦合)關係:(1.打個比方說我如今有一個A這個類,而後A調用B裏面的方法,那麼這個時候就是A依賴B。2.A繼承了B,這個時候A也是依賴了B)全部A和B發生關係的地方都是依賴關係。
更好的描述:不要依賴那些容易變化的具體類。若是你要繼承一個類,從一個抽象類繼承。若是要持有一個類的引用,從一個抽象的類引用。若是要調用一個函數,從一個抽象的函數調用。
4)實現開閉原則的關鍵是抽象化,而且從抽象化導出具體化實現,若是說開閉原則是面向對象設計的目標的話,那麼依賴倒置原則就是面向對象設計的主要手段。
5)依賴倒置於原則的經常使用實現方式之一是在代碼中使用抽象類,而將具體類放到配置文件中。
6)依賴倒置原則是用來解耦合的,使代碼之間的耦合性愈來愈低,就達到了解耦的目的。
類之間的耦合
(高內聚是指一個軟件模塊是由相關性很強的代碼組成,只負責一項任務,也就是常說的單一責任原則。
低耦合是指讓每一個模塊儘量的獨立完成某個特定的子功能。)
倆個類沒有耦合關係就是零耦合關係,可是這只是咱們心目中但願的,很難實現。
發生在倆個具體類(可實例化的)之間,經有一個類對另外一個類直接應用形成的耦合關係就是具體耦合關係。
發生在一個具體類和一個抽象類(或接口)之間,使倆個必修發生關係的類之間存在最大的靈活性的耦合就是抽象耦合關係。(好比說A依賴於B,B是個接口。這就是抽象耦合關係)
7)依賴倒置原則要求客戶端依賴於抽象耦合,以抽象方式耦合是依賴倒置原則的關鍵。
客戶端常常調用服務器端的東西,因此說客戶端依賴服務器端。可是依據依賴倒置原則,就建議使用抽象耦合,依賴抽象類或抽象方法。
在倆個具體的實現了加一層抽象的接口,使其穩定。(最上層依賴的是接口,下面的每層都實現接口,這樣的話無論高層仍是下層都是依賴於接口)
8)依賴注入:
A依賴抽象的C,B也依賴抽象的C。這個時候須要具體的代碼實現B(這個時候就是依賴注入)。
接口注入: 經過接口方法注入實例變量。
某系統提供一個數據轉換模塊,能夠未來自不一樣數據源的數據轉換成多種格式,如能夠轉換來自數據庫的數據、也能夠轉換來自文本文件的數據,轉換後的格式也是XML文件、也能夠是XLS文件等。根據需求的設計以下:
因爲需求的改變,該系統可能須要增長新的數據源或者新的文件格式,每增長一個新的類型的數據源或者新的類型的文件格式,客戶類MainClass都須要修改源代碼,以便使用新的類,但違背了開閉原則。
將倆個具體的方法進行抽象化,同時將倆個文本轉換的方法也進行抽象。而後讓MainClass依賴接口,把變化的東西放到配置文件上。這樣就符合依賴倒置原則了,關於擴展問題也隨之解決了。
5.接口隔離原則(重要性2顆星)
使用多個專門的接口來取代一個統一的接口
1)接口隔離原則是指使用多個專門的接口,而不使用單一的總接口。每個接口應該承擔一種相對獨立的角色,很少很多,不幹不應乾的事,該乾的事都要幹。
2)使用接口隔離原則拆分接口時,首先必須知足單一職責原則,將一組相關的操做定義在一個接口中,且在知足高內聚的前提下,接口中的方法越少越好。
3)能夠在進行系統設計時採用定製服務的方式,即爲不一樣的客戶端提供寬窄不一樣的接口,只提供用戶須要的行爲,而隱藏用戶不須要的行爲。
開發人員針對某個系統的客戶數據顯示模塊設計瞭如圖所示的接口
dataRead()用於從文件中讀取數據
transfromToXML()用於將數據轉換成XML格式
createChart()用於建立圖表,
displayChart()用於顯示圖表
createReport()用於建立文字報表
dislayReport()用於顯示文字報表
client界面,界面調用接口中的方法,方法再進行具體的實現。這樣的設計雖然也實現了須要的功能,而且也知足了依賴倒置原則,但並非很完美。
將每一個功能都進行拆分,每一個類中都是單獨一個實現的方法。這樣就將代碼優化了。
6.合成複用原則(重要性4顆星)
在系統中應該儘可能多使用組合和聚合類關聯關係,儘可能少使用甚至不使用繼承關係
合成複用原則又稱爲組合/聚合複用原則。
儘可能使用對象組合,而不是繼承來達到複用的目的。(面向對象有一個很重要的特色就是儘可能複用一些代碼)
1)合成複用原則就是指在一個新的對象裏經過關聯關係(包括組合關係和聚合關係)來使用一些已有的對象,使之成爲新對象的一部分;新對象經過委派調用已有對象的方法達到複用其已有功能的目的。簡言之:要儘可能使用組合/聚合關係,少用繼承。
2)在面向對象設計中,能夠經過倆種基本方法在不一樣的環境中複用已有的設計和實現,即經過組合/聚合關係或經過繼承。
3)組合/聚合能夠使系統更加靈活,類與類之間的耦合度下降,一個類的變化對其餘類形成的影響相對較少,所以通常首選使用組合/聚合來實現複用;其次才考慮繼承,在使用繼承時,須要嚴格遵循里氏替換原則,有效使用繼承會有助於對問題的理解,下降複雜度,而濫用繼承反而會增長系統構建和維護的難道以及系統的複雜度,所以須要慎重使用繼承複用。
某教學管理系統部分數據庫訪問類設計如圖:
若是須要更換數據庫鏈接方式,如原先採用JDBC鏈接數據庫,如今採用數據庫鏈接池鏈接,則須要修改DBUtil類源代碼。
7.迪米特法則(重要性3顆星)
一個軟件實體對其餘實體的引用越少越好,或者說若是倆個類沒必要彼此直接通訊,那麼這倆個類就不該當發生直接的相互做用,而是經過引入一個第三者發生間接交互
1)不要和「陌生人」說話
2)只與你的直接朋友通訊
3)每個軟件單位對其餘的單位都只有最少的知識,並且侷限於那些於本類單位密切相關的軟件單位
1)迪米特法則又稱爲最少知識法則。
2)迪米特法則來自1987年秋美國東北大學一個名爲「Demeter」的研究項目
3)簡單地說,迪米特法則就是一個軟件實體應對儘量少的與其餘實體發生相互做用。這樣,當一個模塊修改是,就會盡可能少的影響其餘的模塊,擴展會相對容易,這是對軟件實體之間通訊的限制,他要求限制軟件實體之間通訊的寬度和深度。(寬度指涉及到面積,深度是指涉及到的子類數量)
4)在迪米特法則中,對於一個對象,其朋友包括如下幾類:
以參數形式傳入到當前對象方法中的對象
當前對象的成員對象
若是當前對象的成員是一個集合,那麼集合中的元素也都是朋友
5)任何一個對象,若是知足上面的條件之一,就是當前對象的「朋友」,不然就是「陌生人"
6)地面米特法則可分爲狹義法則和廣義法則。在狹義的迪米特法則中,若是倆個類之間沒必要彼此直接通訊,那麼這倆個類就不該該發生直接的相互做用,若是其中的一個類須要調用另外一個類的某一個方法的話,能夠經過第三者轉發這個調用。
上圖中Object A和Object B是相互依賴的關係A裏面的某個方法裏面傳的參數類型是Object B類型,而後能夠通信,如今A想調用C裏面的方法,第一種方法是A和C直接發生關係(可是不提倡,提倡間接發生關係)第二種方法是A調用B,B調用C,這樣就是間接發生關係。
7)迪米特法則的主要用途在於控制信息的過載:
某系統界面類(如Form一、Form2等類)與數據訪問類(如DAO一、DAO2等類)之間的調用關係較爲複雜,如圖所示:
若是數據範文類DAO1發生變化,Form1和Form2都須要發生改變。因此這種耦合關係須要進行解耦。
在系統界面層和數據訪問層之間添加一箇中間層,使倆者之間不進行直接通訊,這樣就大幅度的解耦了。不至於牽一髮而動全身。打個比方說(若是DAO1發生改變,不至於Form1進行改變,有可能只須要改變Controller1裏面的一些方法)。
設計思考與總結
1)對於面向對象的軟件系統來講,在支持可維護性的同時,須要提升系統的可複用性。
2)軟件的複用能夠提升軟件的開發效率,提升軟件質量,節約開發成本,恰當的複用還能夠改善系統的可維護性。
3)單一職責原則要求在軟件系統中,一個類只負責一個功能領域中的相應職責。
4)開閉原則要求一個軟件實體應當對擴展開放,對修改關閉,即在不修改源代碼的基礎上擴展一個系統的行爲。
5)里氏替換原則能夠通俗表述爲在軟件中若是可以使用基類對象,那麼必定可以使用其子類對象。
6)依賴倒置原則要求抽象不該該依賴細節,細節應該依賴抽象;要針對接口編程,不要針對實現編程。
7)接口隔離原則要求客戶端不該該依賴那些它不須要的接口,即將一些大的接口細化成一些小的接口供客戶端使用。
8)合成複用原則要求複用時儘可能使用對象組合,而不是使用繼承。
9)迪米特法則要求一個軟件實體應當儘量少的與其餘實體發生相互做用。
面向對象設計幹了倆件事,一個是類的設計,另外一個是類與類之間的關係設計。主要目的是爲了使代碼達到高內聚,低耦合的目的。
結束:
本篇基本將面向對象的七大原則說完了,這裏多說倆句,面向對象不止七個原則,咱們只是挑裏面比較重要的七個說一說。