面向對象設計原則

對于面向對象軟件系統的設計而言,在支持可維護性的同時,提高系統的可復用性是一個至關重要的問題,如何同時提高一個軟件系統的可維護性和可復用性是面向對象設計需要解決的核心問題之一。在面向對象設計中,可維護性的復用是以設計原則為基礎的。每一個原則都蘊含一些面向對象設計的思想,可以從不同的角度提升一個軟件結構的設計水平。

單一職責原則

單一職責原則(Single Responsibility Principle, SRP):一個類只負責一個功能領域中的相應職責,或者可以定義為:就一個類而言,應該只有一個引起它變化的原因。

單一職責原則告訴我們:一個類不能太“累”!在軟件系統中,一個類(大到模塊,小到方法)承擔的職責越多,它被復用的可能性就越小,而且一個類承擔的職責過多,就相當于將這些職責耦合在一起,當其中一個職責變化時,可能會影響其他職責的運作,因此要將這些職責進行分離,將不同的職責封裝在不同的類中,即將不同的變化原因封裝在不同的類中,如果多個職責總是同時發生改變則可將它們封裝在同一類中。
單一職責原則是實現高內聚、低耦合的指導方針,它是最簡單但又最難運用的原則,需要設計人員發現類的不同職責并將其分離,而發現類的多重職責需要設計人員具有較強的分析設計能力和相關實踐經驗。

開閉原則

開閉原則(Open-Closed Principle, OCP):一個軟件實體應當對擴展開放,對修改關閉。即軟件實體應盡量在不修改原有代碼的情況下進行擴展。

在開閉原則的定義中,軟件實體可以指一個軟件模塊、一個由多個類組成的局部結構或一個獨立的類

任何軟件都需要面臨一個很重要的問題,即它們的需求會隨時間的推移而發生變化。當軟件系統需要面對新的需求時,我們應該盡量保證系統的設計框架是穩定的。如果一個軟件設計符合開閉原則,那么可以非常方便地對系統進行擴展,而且在擴展時無須修改現有代碼,使得軟件系統在擁有適應性和靈活性的同時具備較好的穩定性和延續性。隨著軟件規模越來越大,軟件壽命越來越長,軟件維護成本越來越高,設計滿足開閉原則的軟件系統也變得越來越重要。
為了滿足開閉原則,需要對系統進行抽象化設計,抽象化是開閉原則的關鍵

里氏替換原則

里氏代換原則(Liskov Substitution Principle, LSP):所有引用基類(父類)的地方必須能透明地使用其子類的對象

里氏代換原則告訴我們,在軟件中將一個基類對象替換成它的子類對象,程序將不會產生任何錯誤和異常,反過來則不成立,如果一個軟件實體使用的是一個子類對象的話,那么它不一定能夠使用基類對象。例如:我喜歡動物,那我一定喜歡狗,因為狗是動物的子類;但是我喜歡狗,不能據此斷定我喜歡動物,因為我并不喜歡老鼠,雖然它也是動物。

例如有兩個類,一個類為BaseClass,另一個是SubClass類,并且SubClass類是BaseClass類的子類,那么一個方法如果可以接受一個BaseClass類型的基類對象base的話,如:method1(base),那么它必然可以接受一個BaseClass類型的子類對象sub,method1(sub)能夠正常運行。反過來的代換不成立,如一個方法method2接受BaseClass類型的子類對象sub為參數:method2(sub),那么一般而言不可以有method2(base),除非是重載方法。

為什么要里氏替換原則?

對于基類中定義的所有子程序,用在它的任何一個派生類中時的含義都應該是相同的。
這樣繼承才不會增加復雜度,基類才能真正被復用,而派生類也能夠在基類的基礎上增加新的行為。
如果我們必須要不斷地思考不同派生類的實現在語義上的差異,繼承就只會增加復雜度了。
所以遵循里氏替換規則就是為了 降低繼承帶來的復雜度

里氏代換原則是實現開閉原則的重要方式之一,由于使用基類對象的地方都可以使用子類對象,因此在程序中盡量使用基類類型來對對象進行定義,而在運行時再確定其子類類型,用子類對象來替換父類對象

在使用里氏代換原則時需要注意如下幾個問題:

  1. 子類的所有方法必須在父類中聲明,或子類必須實現父類中聲明的所有方法。根據里氏代換原則,為了保證系統的擴展性,在程序中通常使用父類來進行定義,如果一個方法只存在子類中,在父類中不提供相應的聲明,則無法在以父類定義的對象中使用該方法。
  2. 我們在運用里氏代換原則時,盡量把父類設計為抽象類或者接口,讓子類繼承父類或實現父接口,并實現在父類中聲明的方法,運行時,子類實例替換父類實例,我們可以很方便地擴展系統的功能,同時無須修改原有子類的代碼,增加新的功能可以通過增加一個新的子類來實現。里氏代換原則是開閉原則的具體實現手段之一。
  3. Java語言中,在編譯階段,Java編譯器會檢查一個程序是否符合里氏代換原則,這是一個與實現無關的、純語法意義上的檢查,但Java編譯器的檢查是有局限的。

依賴倒轉原則

依賴倒轉原則(Dependency Inversion Principle, DIP):抽象不應該依賴于細節,細節應當依賴于抽象。換言之,要針對接口編程,而不是針對實現編程。

依賴倒轉原則要求我們在程序代碼中傳遞參數時或在關聯關系中,盡量引用層次高的抽象層類,即使用接口和抽象類進行變量類型聲明、參數類型聲明、方法返回類型聲明,以及數據類型的轉換等,而不要用具體類來做這些事情。為了確保該原則的應用,一個具體類應當只實現接口或抽象類中聲明過的方法,而不要給出多余的方法,否則將無法調用到在子類中增加的新方法。

在引入抽象層后,系統將具有很好的靈活性,在程序中盡量使用抽象層進行編程,而將具體類寫在配置文件中,這樣一來,如果系統行為發生變化,只需要對抽象層進行擴展,并修改配置文件,而無須修改原有系統的源代碼,在不修改的情況下來擴展系統的功能,滿足開閉原則的要求。

在實現依賴倒轉原則時,我們需要針對抽象層編程,而將具體類的對象通過依賴注入(DependencyInjection, DI)的方式注入到其他對象中,依賴注入是指當一個對象要與其他對象發生依賴關系時,通過抽象來注入所依賴的對象。常用的注入方式有三種,分別是:構造注入,設值注入(Setter注入)和接口注入。構造注入是指通過構造函數來傳入具體類的對象,設值注入是指通過Setter方法來傳入具體類的對象,而接口注入是指通過在接口中聲明的業務方法來傳入具體類的對象。這些方法在定義時使用的是抽象類型,在運行時再傳入具體類型的對象,由子類對象來覆蓋父類對象。

在大多數情況下,這三個設計原則會同時出現,開閉原則是目標,里氏代換原則是基礎,依賴倒轉原則是手段,它們相輔相成,相互補充,目標一致,只是分析問題時所站角度不同而已。

接口隔離原則

接口隔離原則(Interface Segregation Principle, ISP):使用多個專門的接口,而不使用單一的總接口,即客戶端不應該依賴那些它不需要的接口。

根據接口隔離原則,當一個接口太大時,我們需要將它分割成一些更細小的接口,使用該接口的客戶端僅需知道與之相關的方法即可。每一個接口應該承擔一種相對獨立的角色,不干不該干的事,該干的事都要干。這里的“接口”往往有兩種不同的含義:一種是指一個類型所具有的方法特征的集合,僅僅是一種邏輯上的抽象;另外一種是指某種語言具體的“接口”定義,有嚴格的定義和結構,比如Java語言中的interface。對于這兩種不同的含義,ISP的表達方式以及含義都有所不同:

  1. 當把“接口”理解成一個類型所提供的所有方法特征的集合的時候,這就是一種邏輯上的概念,接口的劃分將直接帶來類型的劃分。可以把接口理解成角色,一個接口只能代表一個角色,每個角色都有它特定的一個接口,此時,這個原則可以叫做“角色隔離原則”。
  2. 如果把“接口”理解成狹義的特定語言的接口,那么ISP表達的意思是指接口僅僅提供客戶端需要的行為,客戶端不需要的行為則隱藏起來,應當為客戶端提供盡可能小的單獨的接口,而不要提供大的總接口。在面向對象編程語言中,實現一個接口就需要實現該接口中定義的所有方法,因此大的總接口使用起來不一定很方便,為了使接口的職責單一,需要將大接口中的方法根據其職責不同分別放在不同的小接口中,以確保每個接口使用起來都較為方便,并都承擔某一單一角色。接口應該盡量細化,同時接口中的方法應該盡量少,每個接口中只包含一個客戶端(如子模塊或業務邏輯類)所需的方法即可,這種機制也稱為“定制服務”,即為不同的客戶端提供寬窄不同的接口。

在使用接口隔離原則時,我們需要注意控制接口的粒度,接口不能太小,如果太小會導致系統中接口泛濫,不利于維護;接口也不能太大,太大的接口將違背接口隔離原則,靈活性較差,使用起來很不方便。一般而言,接口中僅包含為某一類用戶定制的方法即可,不應該強迫客戶依賴于那些它們不用的方法。

合成復用原則

合成復用原則(Composite Reuse Principle, CRP):盡量使用對象組合,而不是繼承來達到復用的目的。

合成復用原則就是在一個新的對象里通過關聯關系(包括組合關系和聚合關系)來使用一些已有的對象,使之成為新對象的一部分;新對象通過委派調用已有對象的方法達到復用功能的目的。簡言之:復用時要盡量使用組合/聚合關系(關聯關系),少用繼承

在面向對象設計中,可以通過兩種方法在不同的環境中復用已有的設計和實現,即通過組合/聚合關系或通過繼承,但首先應該考慮使用組合/聚合,組合/聚合可以使系統更加靈活,降低類與類之間的耦合度,一個類的變化對其他類造成的影響相對較少;其次才考慮繼承,在使用繼承時,需要嚴格遵循里氏代換原則,有效使用繼承會有助于對問題的理解,降低復雜度,而濫用繼承反而會增加系統構建和維護的難度以及系統的復雜度,因此需要慎重使用繼承復用。

通過繼承來進行復用的主要問題在于繼承復用會破壞系統的封裝性,因為繼承會將基類的實現細節暴露給子類,由于基類的內部細節通常對子類來說是可見的,所以這種復用又稱“白箱”復用,如果基類發生改變,那么子類的實現也不得不發生改變;從基類繼承而來的實現是靜態的,不可能在運行時發生改變,沒有足夠的靈活性;而且繼承只能在有限的環境中使用(如類沒有聲明為不能被繼承)。

迪米特法則

迪米特法則(Law of Demeter, LoD):一個軟件實體應當盡可能少地與其他實體發生相互作用。

如果一個系統符合迪米特法則,那么當其中某一個模塊發生修改時,就會盡量少地影響其他模塊,擴展會相對容易,這是對軟件實體之間通信的限制,迪米特法則要求限制軟件實體之間通信的寬度和深度。迪米特法則可降低系統的耦合度,使類與類之間保持松散的耦合關系。

迪米特法則還有幾種定義形式,包括:不要和“陌生人”說話、只與你的直接朋友通信等,在迪米特法則中,對于一個對象,其朋友包括以下幾類:

  1. 當前對象本身(this)
  2. 以參數形式傳入到當前對象方法中的對象
  3. 當前對象的成員對象
  4. 如果當前對象的成員對象是一個集合,那么集合中的元素也都是朋友
  5. 當前對象所創建的對象

任何一個對象,如果滿足上面的條件之一,就是當前對象的“朋友”,否則就是“陌生人”。在應用迪米特法則時,一個對象只能與直接朋友發生交互,不要與“陌生人”發生直接交互,這樣做可以降低系統的耦合度,一個對象的改變不會給太多其他對象帶來影響。
迪米特法則要求我們在設計系統時,應該盡量減少對象之間的交互,如果兩個對象之間不必彼此直接通信,那么這兩個對象就不應當發生任何直接的相互作用,如果其中的一個對象需要調用另一個對象的某一個方法的話,可以通過第三者轉發這個調用。簡言之,就是通過引入一個合理的第三者來降低現有對象之間的耦合度。

在將迪米特法則運用到系統設計中時,要注意下面的幾點:在類的劃分上,應當盡量創建松耦合的類,類之間的耦合度越低,就越有利于復用,一個處在松耦合中的類一旦被修改,不會對關聯的類造成太大波及;在類的結構設計上,每一個類都應當盡量降低其成員變量和成員函數的訪問權限;在類的設計上,只要有可能,一個類型應當設計成不變類;在對其他類的引用上,一個對象對其他對象的引用應當降到最低。

參考鏈接

開閉原則
里氏代換原則
依賴倒轉原則
接口隔離原則
迪米特法則

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容