1. 程式人生 > 其它 >面向物件七大原則

面向物件七大原則

 面向物件七大原則

1 開閉原則(The Open-Closed Principle ,OCP)

開閉原則是面向物件的可複用設計的第一塊基石,它是最重要的面向物件設計原則。開閉原則由Bertrand Meyer於1988年提出,其定義如下:

開閉原則:軟體實體應當對擴充套件開放,對修改關閉。

根據開閉原則,在設計一個軟體系統模組(類,方法)的時候,應該可以在不修改原有的模組(修改關閉)的基礎上,能擴充套件其功能(擴充套件開放)。

擴充套件開放:某模組的功能是可擴充套件的,則該模組是擴充套件開放的。軟體系統的功能上的可擴充套件性要求模組是擴充套件開放的。
修改關閉:某模組被其他模組呼叫,如果該模組的原始碼不允許修改,則該模組修改關閉的。軟體系統的功能上的穩定性,持續性要求模組是修改關閉的。

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

為了滿足開閉原則,需要對系統進行抽象化設計,抽象化是開閉原則的關鍵。在Java、C#等程式語言中可以為系統定義一個相對穩定的抽象層,而降不同的實現行為移至具體的實現層中完成。在很多面向物件程式語言中都提供了介面、抽象類等機制,可以通過它們定義系統的抽象層,再通過具體類來進行擴充套件。如果需要修改系統的行為,無須對抽象層進行任何改動,只需要增加新的具體類來實現新的業務功能即可,實現在不修改已有程式碼的基礎上擴充套件系統的功能,達到開閉原則的要求。

2 單一職責原則

單一職責原則是最簡單的面向物件設計原則,它用於控制類的粒度大小。單一職責原則的定義如下:

單一職責原則:一個物件應該只包含單一的職責,並且該職責被完整地封裝在一個類中。
單一職責原則的另一種定義方式:就一個類而言,應該僅有一個引起它變化的原因。

在軟體系統,一個類(大到模組,小中到方法)承擔的職責越多,它被複用的可能性越小,而且一個類承擔的責任過多,相當於將這些職責耦合在一起,當其中一個職責變化時,可能會影響其它職責的運作,因此要將這些職責進行分離,將不同的職責封裝在不同的類中,即將不同變化原因封裝在不同的類中,如果多個職責總是同時發生改變,則可將他們封裝在同一個類中。

單一職責原則是實現高內聚、低耦合

的指導方針,它是最簡單但又最難運用的原則,需要設計人員發現類的不同職責並將其分離,而發現類的多重職責需要設計人員具有較強的分析設計能力和相關實踐經驗。

3 里氏替換原則(Liskov Substitution Principle ,LSP)

里氏代換原則:所有引用基類的地方必須能透明地使用其子類的物件

也就是說,只有滿足以下2個條件的OO設計才可被認為是滿足了LSP原則:

不應該在程式碼中出現if/else之類對派生類型別進行判斷的條件。

派生類應當可以替換基類並出現在基類能夠出現的任何地方,或者說如果我們把程式碼中使用基類的地方用它的派生類所代替,程式碼還能正常工作。

裡式替換原則的引申意義:子類可以擴充套件父類的功能,但不能改變父類原有的功能。

里氏代換原則表明,在軟體中將一個基類物件替換成它的子類物件,程式將不會產生任何錯誤和異常,反過來則不成立,如果一個軟體實體使用的是一個子類物件,那麼它不一定能夠使用基類物件。例如我喜歡動物,那我一定喜歡狗,因為狗是動物的子類,但是我喜歡狗,不能據此判定我喜歡所有的動物。

里氏替換原則是實現開閉原則的重要方式之一,由於在使用基類物件的地方都可以使用子類物件,因此在程式中儘量使用基類型別來對物件進行定義,而在執行時再確定其子類型別,用子類物件來替換父類物件。
在運用里氏替換原則時應該將父類設計為抽象類或者介面,讓子類繼承父類或實現父介面,並實現在父類中宣告方法,在執行時子類例項替換父類例項,可以很方便地擴充套件系統的功能,無須修改原有子類的程式碼,增加新的功能可以通過增加一個新的子類來實現。

4 迪米特原則(最少知道原則)(Law of Demeter ,LoD)

第米特原則:迪米特原則(Law of Demeter)又叫最少知道原則(Least Knowledge Principle),可以簡單說成:talk only to your immediate friends,只與你直接的朋友們通訊,不要跟“陌生人”說話。

迪米特法則可以簡單說成:talk only to your immediate friends。 對於OOD來說,又被解釋為下面幾種方式:一個軟體實體應當儘可能少的與其他實體發生相互作用。每一個軟體單位對其他的單位都只有最少的知識,而且侷限於那些與本單位密切相關的軟體單位。

迪米特法則的初衷在於降低類之間的耦合。由於每個類儘量減少對其他類的依賴,因此,很容易使得系統的功能模組功能獨立,相互之間不存在(或很少有)依賴關係。

迪米特法則不希望類之間建立直接的聯絡。如果真的有需要建立聯絡,也希望能通過它的友元類來轉達。因此,應用迪米特法則有可能造成的一個後果就是:系統中存在大量的中介類,這些類之所以存在完全是為了傳遞類之間的相互呼叫關係——這在一定程度上增加了系統的複雜度。

如果兩個類不必彼此直接通訊,那麼這兩個類就不應當發生直接的相互作用。如果其中的一個類需要呼叫另一個類的某一個方法的話,可以通過第三者轉發這個呼叫。

朋友圈的確定

“朋友”條件:

1)當前物件本身(this)

2)以參量形式傳入到當前物件方法中的物件

3)當前物件的例項變數直接引用的物件

4)當前物件的例項變數如果是一個聚集,那麼聚集中的元素也都是朋友

5)當前物件所建立的物件

任何一個物件,如果滿足上面的條件之一,就是當前物件的“朋友”;否則就是“陌生人”。

狹義的迪米特法則的缺點:

在系統裡造出大量的小方法,這些方法僅僅是傳遞間接的呼叫,與系統的業務邏輯無關。

遵循類之間的迪米特法則會是一個系統的區域性設計簡化,因為每一個區域性都不會和遠距離的物件有直接的關聯。但是,這也會造成系統的不同模組之間的通訊效率降低,也會使系統的不同模組之間不容易協調。

5 介面隔離原則(Interface Segregation Principle, ISP)

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

換句話說,使用多個專門的介面比使用單一的總介面總要好。

它包含了2層意思:

介面的設計原則:介面的設計應該遵循最小介面原則,不要把使用者不使用的方法塞進同一個接口裡。如果一個介面的方法沒有被使用到,則說明該介面過胖,應該將其分割成幾個功能專一的介面。

介面的依賴(繼承)原則:如果一個介面a繼承另一個介面b,則介面a相當於繼承了介面b的方法,那麼繼承了介面b後的介面a也應該遵循上述原則:不應該包含使用者不使用的方法。 反之,則說明介面a被b給汙染了,應該重新設計它們的關係。

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

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

如果使用者被迫依賴他們不使用的介面,當介面發生改變時,他們也不得不跟著改變。換而言之,一個使用者依賴了未使用但被其他使用者使用的介面,當其他使用者修改該介面時,依賴該介面的所有使用者都將受到影響。這顯然違反了開閉原則,也不是我們所期望的。

總而言之,介面分隔原則指導我們:

一個類對一個類的依賴應該建立在最小的介面上

建立單一介面,不要建立龐大臃腫的介面

儘量細化介面,介面中的方法儘量少

6 依賴倒置原則(Dependency Inversion Principle ,DIP)

如果說開閉原則是面向物件設計的目標,那麼依賴倒轉原則就是面向物件設計的主要實現機制之一,它是系統抽象化的具體實現。依賴倒轉原則是Robert C. Martin在1996年為C++Reporter所寫的專欄Engineering Notebook 的第三篇,後來加入到他在2002年出版的經典著作 Agile Software Development, Principles, Patterns, and Pratices一書中。

依賴倒轉原則定義如下:
高層模組不應該依賴底層模組,它們都應該依賴抽象。抽象不應該依賴於細節,細節應該依賴於抽象。

簡單來說,依賴倒轉原則要求針對介面程式設計,不要針對實現程式設計。

依賴倒轉原則要求在程式程式碼中傳遞引數時或在關聯關係中儘量引用層次高的抽象層類,即使用介面和抽象類進行變數型別宣告、引數宣告、引數型別宣告、方法返回型別宣告,以及資料型別的轉換等,而不要用具體類來做這些事情。為了確保該原則的應用,一個具體類應當只實現介面或抽象類中宣告過的方法,而不要給出多餘的方法,否則將無法呼叫到子類中增加的新方法。

在引入抽象層後,系統將具有更好的靈活性,在程式中儘量使用抽象層進行程式設計,而將具體類寫在配置檔案中,這樣如果系統行為發生變化,只需要對抽象層進行擴充套件,並修改配置檔案,而無須修改原有系統的原始碼,在不修改的情況下來擴充套件系統的功能,滿足開閉原則的需求。

7 合成複用原則(Composite/Aggregate Reuse Principle ,CARP)

合成複用原則又稱為組合/聚合複用原則(Composition/Aggregate Reuse Principle, CARP),其定 義如下:

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

合成複用原則就是在一個新的物件裡通過關聯關係(包括組合關係和聚合關係)來使用一些 已有的物件,使之成為新物件的一部分;新物件通過委派呼叫已有物件的方法達到複用功能 的目的。簡言之:複用時要儘量使用組合/聚合關係(關聯關係),少用繼承。

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

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