1. 程式人生 > >面向物件中七個的設計原則

面向物件中七個的設計原則

七大設計原則:

開閉原則
單一職責原則
里氏替換原則
依賴倒置原則
介面隔離原則
最少知識原則(迪米特法則)
少用繼承多用組合(合成複用)

這些原則的作用:可以讓自己設計實現出來的軟體系統更加穩定,容易維護,並具有一致性

(一)開閉原則

開閉原則定義 :一個軟體實體應當對擴充套件開放,對修改關閉。也就是說在設計一個模組的時候,應當使這個模組可以在不被修改的前提下被擴充套件,即實現在不修改原始碼的情況下改變這個模組的行為。
就是在最開始設計軟體的時候考慮以後可能修改的地方定義介面,如果後期需要修改功能只需要重新設計一個實現方法連線需要修改的介面就行了。
開閉原則總結:面對需求,對程式的改動是通過增加新程式碼進行的,而不是改變原來的程式碼。

(1)多用抽象類
在設計類時,對於擁有共同功能的相似類進行抽象化處理,將公用的功能部分放到抽象類中,所有的操作都呼叫子類。這樣,在需要對系統進行功能擴充套件時,只需要依據抽象類實現新的子類即可。如圖
所示,在擴充套件子類時,不僅可以擁有抽象類的共有屬性和共有函式,還可以擁有自定義的屬性和函式。



(2)多用介面
與抽象類不同,介面只定義子類應該實現的介面函式,而不實現公有的功能。在現在大多數的軟體開發中,都會為實現類定義介面,這樣在擴充套件子類時實現該介面。如果要改換原有的實現,只需要改換一個實現類即可。如圖10-2所示,各子類由介面類定義了介面函式,只需要在不同的子類中編寫不同的實現即可,當然也可以實現自有的函式。

有一句話概括很不錯:用抽象構建框架,用實現擴充套件細節。因為抽象靈活性好,適應性廣,只要抽象的合理,可以基本保持軟體架構的穩定。而軟體中易變的細節,我們用從抽象派生的實現類來進行擴充套件,當軟體需要發生變化時,我們只需要根據需求重新派生一個實現類來擴充套件就可以了。當然前提是我們的抽象要合理,要對需求的變更有前瞻性和預見性才行。

開閉原則總結:面對需求,對程式的改動是通過增加新程式碼進行的,而不是改變原來的程式碼。

(二)依賴倒轉原則

核心:要依賴於抽象,不要依賴於具體的實現

1.依賴倒轉原則三層含義:
i. 高層模組不應該依賴低層模組,它們都應該依賴抽象。
ii.抽象不應該依賴於細節(實現),細節應該依賴於抽象。
iii. 要針對介面程式設計,不要針對實現程式設計。 (例如我們去呼叫某個方法的時候,通過介面宣告物件,然後通過物件去呼叫)

例如如下圖:左邊DatabaseSouce是從資料庫中取得資料,ServerSouce都是從伺服器中取得資料,這兩個都是資料來源,右邊XML和Json都是解析資料的工具類,所以在MainClass中我們要從DatabaseSouce或者ServerSouce中取得資料然後利用XML和Json工具類去解析資料這樣我們就相當於在MainClass中需要去呼叫左邊的資料來源和右邊的解析資料工具類,這裡DatabaseSouce\ServerSouce\Json\XML都是低層模組,而MainClass屬於高層模組,這樣就違反了依賴倒轉原則,高層模組依賴於了低層模組,這樣一旦我們的低層模組發生了改變增加了功能或者需要切換到其他的資料來源那麼我們的高層模組也要去修改程式碼。而我們軟體系統需要的是讓其可以擴充套件而不影響其他模組功能。




下面我們去重構下,看看該如何去設計才能讓其低層模組增加功能而不改變高層模組的程式碼。只要將資料來源和解析工具分別抽象出來一個抽象類或者介面,這樣低層模組一旦發生改變就不會影響到我們的高層模組,所以這裡遵循了我們的原則高層模組不應該依賴低層模組,針對介面程式設計,不針對實現程式設計。


優點:這樣的話修改低層模組不會影響到高層模組,減小了它們之間的耦合度,增強系統的穩定性。

總結:如果編寫時考慮的都是如何針對抽象程式設計而不是針對細節程式設計,即程式中所有的依賴關係都是終止於抽象類或者介面,那就是面向物件的設計,反之那就是過程化的設計了。

(三)里氏代換原則

里氏代換原則分析
i. 在軟體中如果能夠使用基類物件,那麼一定能夠使用其子類物件。把基類都替換成它的子類,程式將不會產生任何錯誤和異常,反過來則不成立,如果一個軟體實體使用的是一個子類的話,那麼它不一定能夠使用基類。
ii. 里氏代換原則是實現開閉原則的重要方式之一,由於使用基類物件的地方都可以使用子類物件,因此在程式中儘量使用基類型別來對物件進行定義,而在執行時再確定其子類型別,用子類物件來替換父類物件。

也就是說在有父類的地方全部替換成子類而不影響整個程式的執行
總結:子型別必須能夠替換掉它們的父型別。

(四)單一職責原則

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

分析
i. 一個類(或者大到模組,小到方法)承擔的職責越多,它被複用的可能性越小,而且如果一個類承擔的職責過多,就相當於將這些職責耦合在一起,當其中一個職責變化時,可能會影響其他職責的運作。
ii.類的職責主要包括兩個方面:資料職責和行為職責,資料職責通過其屬性來體現,而行為職責通過其方法來體現。
iii.單一職責原則是實現高內聚、低耦合的指導方針,在很多程式碼重構手法中都能找到它的存在,它是最簡單但又最難運用的原則,需要設計人員發現類的不同職責並將其分離,而發現類的多重職責需要設計人員具有較強的分析設計能力和相關重構經驗。

單一職責表示的就是例如定義了一個主角類,那麼這個主角類裡面只能有這個主角的一些功能,而不能去實現其他的功能。

總結:就一個類而言,應該僅有一個引起它變化的原因。

(五)介面隔離原則

定義
i. 客戶端不應該依賴那些它不需要的介面。
ii. 一旦一個介面太大,則需要將它分割成一些更細小的介面,使用該介面的客戶端僅需知道與之相關的方法即可。
分析
i. 介面隔離原則是指使用多個專門的介面,而不使用單一的總介面。每一個介面應該承擔一種相對獨立的角色,不多不少,不幹不該乾的事,該乾的事都要幹。
ii. 使用介面隔離原則拆分介面時,首先必須滿足單一職責原則,將一組相關的操作定義在一個介面中,且在滿足高內聚的前提下,介面中的方法越少越好。
iii. 可以在進行系統設計時採用定製服務的方式,即為不同的客戶端提供寬窄不同的介面,只提供使用者需要的行為,而隱藏使用者不需要的行為

例如下圖有三個模組A,B,C這三個模組都需要呼叫介面IOperator裡面三個不同的方法,然而在這裡比如我們只要使用A模組,但是接口裡面就指會呼叫OperatorA方法而其他的兩個方法是無用的。並且有可能在A模組裡面有可能粗心呼叫錯方法。所以我們可以使用介面隔離原則降低其耦合度。


下面看看可以如何去重構以降低其耦合度。這裡我讓每個模組都分別繼承一個介面然後再在一個類裡面去實現這些介面中的方法,這樣當我們呼叫OperatorA的時候就不會出現其他兩個方法,大大降低了耦合度


總結:類應該完全依賴相應的專門的介面

(六)合成複用原則

定義
i. 儘量使用物件組合,而不是繼承來達到複用的目的。
分析
i. 合成複用原則就是指在一個新的物件裡通過關聯關係(包括組合關係和聚合關係)來使用一些已有的物件,使之成為新物件的一部分;新物件通過委派呼叫已有物件的方法達到複用其已有功能的目的。簡言之:要儘量使用組合/聚合關係,少用繼承。
ii. 在面向物件設計中,可以通過兩種基本方法在不同的環境中複用已有的設計和實現,即通過組合/聚合關係或通過繼承。
a) 繼承複用:實現簡單,易於擴充套件。破壞系統的封裝性;從基類繼承而來的實現是靜態的,不可能在執行時發生改變,沒有足夠的靈活性;只能在有限的環境中使用。(“白箱”複用 )
b) 組合/聚合複用:耦合度相對較低,選擇性地呼叫成員物件的操作;可以在執行時動態進行。(“黑箱”複用 )
iii. 組合/聚合可以使系統更加靈活,類與類之間的耦合度降低,一個類的變化對其他類造成的影響相對較少,因此一般首選使用組合/聚合來實現複用;其次才考慮繼承,在使用繼承時,需要嚴格遵循里氏代換原則,有效使用繼承會有助於對問題的理解,降低複雜度,而濫用繼承反而會增加系統構建和維護的難度以及系統的複雜度,因此需要慎重使用繼承複用。

總結:類中應用,儘量使用物件組合而不是用繼承來達到複用的目的。

(七)迪米特法則(最少知識法則)

定義
i. 每一個軟體單位對其他的單位都只有最少的知識,而且侷限於那些與本單位密切相關的軟體單位。
分析
i.迪米特法則就是指一個軟體實體應當儘可能少的與其他實體發生相互作用。這樣,當一個模組修改時,就會盡量少的影響其他的模組,擴充套件會相對容易,這是對軟體實體之間通訊的限制,它要求限制軟體實體之間通訊的寬度和深度。
ii. 狹義的迪米特法則:可以降低類之間的耦合,但是會在系統中增加大量的小方法並散落在系統的各個角落,它可以使一個系統的區域性設計簡化,因為每一個區域性都不會和遠距離的物件有直接的關聯,但是也會造成系統的不同模組之間的通訊效率降低,使得系統的不同模組之間不容易協調。
iii. 廣義的迪米特法則:指對物件之間的資訊流量、流向以及資訊的影響的控制,主要是對資訊隱藏的控制。資訊的隱藏可以使各個子系統之間脫耦,從而允許它們獨立地被開發、優化、使用和修改,同時可以促進軟體的複用,由於每一個模組都不依賴於其他模組而存在,因此每一個模組都可以獨立地在其他的地方使用。一個系統的規模越大,資訊的隱藏就越重要,而資訊隱藏的重要性也就越明顯。
iv. 迪米特法則的主要用途在於控制資訊的過載。

例如下圖Form為某個系統的介面類,而下面DAO為各個模組,而我們需要實現Form1介面功能的時候就需要呼叫DAO1和DAO2才能實現該介面功能的實現,以此類推要實現Form2也需要呼叫DAO1和DAO2,這樣的話就會顯得很亂,各個功能相互呼叫,並且當我們修改了DAO1那麼就會影響Form1和Form2,所以這裡就可以使用迪米特法則來重構程式碼。



下圖是重構後的,在Form1和DAO直接新增一個Controller,把那些需要組合的功能都放到Controller裡面,然後就是Form1呼叫的方法放到Controller1裡面,然後Controller1分別去呼叫DAO1和DAO2,這樣每一個Form只需要引用一個Controller,Controller再去呼叫多個DAO.這樣的話當我們修改了DAO1我們只需要去修改Controller1就可以了這樣就不會像上面一樣有可能修改一個DAO就需要修改幾個Form。這樣也大大降低了耦合性。

總結:一個軟體實體應當儘可能少的與其他實體發生相互作用

要使我們的程式設計更加靈活,封裝性更好,複用性更高,易於維護我們需要我們的這些原則建設我們規範我們的程式。這樣我們編寫的才是高質量的程式。

設計模式總結



作者:_涼笙
連結: https://www.jianshu.com/p/1f58bb149097
來源:簡書