設計模式六大原則

Dinopell發表於2024-07-09

一、單一職責原則(Single Responsibility Principle)

定義:一個類只負責一個功能領域中的相應職責,或者可以定義為:就一個類而言,應該只有一個引起它變化的原因。
問題由來:類T負責兩個不同的職責:職責P1,職責P2。當由於職責P1需求發生改變而需要修改類T時,有可能會導致原本執行正常的職責P2功能發生故障。
1、單一職責原則,實現類要職責單一;
2、里氏替換原則,不要破壞繼承體系;
3、依賴倒置原則,要面向介面程式設計;
4、介面隔離原則,在設計介面的時候要精簡單一;
5、迪米特原則,要降低耦合;
6、開閉原則,要對擴充套件開放,對修改關閉。
單一職責原的優點有:
1.可以降低類的複雜度,一個類只負責一項職責,其邏輯肯定要比負責多項職責簡單的多;
2.提高類的可讀性,提高系統的可維護性;
3.變更引起的風險降低,變更是必然的,如果單一職責原則遵守的好,當修改一個功能時,可以顯著降低對其他功能的影響。

二.開閉原則(Open-Closed Principle, OCP)

定義:一個軟體實體應當對擴充套件開放,對修改關閉。即軟體實體應儘量在不修改原有程式碼的情況下進行擴充套件
問題由來:任何軟體都需要面臨一個很重要的問題,即它們的需求會隨時間的推移而發生變化。因為變化,升級和維護等原因,如果需要對軟體原有程式碼進行修改,可能會給舊程式碼引入錯誤,也有可能會使我們不得不對整個功能進行重構,並且需要原有程式碼經過重新測試,所以當軟體需要變化時,儘量透過擴充套件軟體實體的行為來實現變化,而不是透過修改已有的程式碼來實現使我們需要的。
為了滿足開閉原則,需要對系統進行抽象化設計,抽象化是開閉原則的關鍵。在Java、C#等程式語言中,可以為系統定義一個相對穩定的抽象層,而將不同的實現行為移至具體的實現層中完成。如果需要修改系統的行為,無須對抽象層進行任何改動,只需要增加新的具體類來實現新的業務功能即可,實現在不修改已有程式碼的基礎上擴充套件系統的功能,達到開閉原則的要求。
為什麼使用開閉原則:
第一:開閉原則非常有名,只要是物件導向程式設計,在開發時都會強調開閉原則
第二:開閉原則是最基礎的設計原則,其它的五個設計原則都是開閉原則的具體形態,也就是說其它的五個設計原則是指導設計的工具和方法,而開閉原則才是其精神領袖。依照Java語言的稱謂,開閉原則是抽象類,而其它的五個原則是具體的實現類。
第三:開閉原則可以提高複用性
在物件導向的設計中,所有的邏輯都是從原子邏輯組合而來,而不是在一個類中獨立實現一套業務邏輯。只有這樣的程式碼才可以複用,邏輯粒度越小,被複用的可能性越大。為什麼要複用呢?複用可以減少程式碼的重複,避免相同的邏輯分散在多個角落,減少維護人員的工作量以及系統變化時產生bug的機會。怎麼才能提高複用率呢?設計者需要縮小邏輯粒度,直到一個邏輯不可以分為止。
第四:開閉原則可以提高維護性
一款軟體量產後,維護人員的工作不僅僅對資料進行維護,還可能要對程式進行擴充套件,維護人員最樂意的事是擴充套件一個類,而不是修改一個類。讓維護人員讀懂原有程式碼,再進行修改,是一件非常痛苦的事情,不要讓他在原有的程式碼海洋中游蕩後再修改,那是對維護人員的折磨和摧殘。
第五:物件導向開發的要求
萬物皆物件,我們要把所有的事物抽象成物件,然後針對物件進行操作,但是萬物皆發展變化,有變化就要有策略去應對,怎麼快速應對呢?這就需要在設計之初考慮到儘可能多變化的因素,然後留下介面,等待“可能”轉變為“現實”。
如何使用開閉原則
第一:抽象約束
抽象是對一組事物的通用描述,沒有具體的實現,也就表示它可以有非常多的可能性,可以跟隨需求的變化而變化。因此,透過介面或抽象類可以約束一組可能變化的行為,並且能夠實現對擴充套件開放,其包含三層含義:
1.透過介面或抽象類約束擴散,對擴充套件進行邊界限定,不允許出現在介面或抽象類中不存在的public方法。
2.引數型別,引用物件儘量使用介面或抽象類,而不是實現類,這主要是實現里氏替換原則的一個要求
3.抽象層儘量保持穩定,一旦確定就不要修改
第二:後設資料(metadata)控制元件模組行為
程式設計是一個很苦很累的活,那怎麼才能減輕壓力呢?答案是儘量使用後設資料來控制程式的行為,減少重複開發。什麼是後設資料?用來描述環境和資料的資料,通俗的說就是配置引數,引數可以從檔案中獲得,也可以從資料庫中獲得。
第三:制定專案章程
在一個團隊中,建立專案章程是非常重要的,因為章程是所有開發人員都必須遵守的約定,對專案來說,約定優於配置。這比透過介面或抽象類進行約束效率更高,而擴充套件性一點也沒有減少
第四:封裝變化
對變化封裝包含兩層含義:
(1)將相同的變化封裝到一個介面或抽象類中
(2)將不同的變化封裝到不同的介面或抽象類中,不應該有兩個不同的變化出現在同一個介面或抽象類中。 封裝變化,也就是受保護的變化,找出預計有變化或不穩定的點,我們為這些變化點建立穩定的介面。

三、里氏代換原則(Liskov Substitution Principle, LSP)

定義:里氏代換原則(Liskov Substitution Principle, LSP):所有引用基類(父類)的地方必須能透明地使用其子類的物件。
繼承優點
程式碼共享,減少建立類的工作量,每個子類都擁有父類的方法和屬性;提高程式碼的重用性;子類可以形似父類,但又異於父類;提高程式碼的可擴充套件性,實現父類的方法就可以“為所欲為”了;提高產品或專案的開放性。
繼承缺點
繼承是侵入性的。只要繼承,就必須擁有父類的所有屬性和方法;降低程式碼的靈活性。子類必須擁有父類的屬性和方法;
增強了耦合性。當父類的常量、變數和方法被修改時,必需要考慮子類的修改,而且在缺乏規範的環境下,這種修改可能帶來非常糟糕的結果;大片的程式碼需要重構。
克服繼承的缺點——里氏替換原則
從整體上來看,利大於弊。
里氏代換原則告訴我們,在軟體中將一個基類物件替換成它的子類物件,程式將不會產生任何錯誤和異常,反過來則不成立,如果一個軟體實體使用的是一個子類物件的話,那麼它不一定能夠使用基類物件。例如:我喜歡動物,那我一定喜歡狗,因為狗是動物的子類;但是我喜歡狗,不能據此斷定我喜歡動物,因為我並不喜歡老鼠,雖然它也是動物。
里氏代換原則是實現開閉原則的重要方式之一,由於使用基類物件的地方都可以使用子類物件,因此在程式中儘量使用基類型別來對物件進行定義,而在執行時再確定其子類型別,用子類物件來替換父類物件。
在使用里氏代換原則時需要注意如下幾個問題:
(1)子類的所有方法必須在父類中宣告,或子類必須實現父類中宣告的所有方法。根據里氏代換原則,為了保證系統的擴充套件性,在程式中通常使用父類來進行定義,如果一個方法只存在子類中,在父類中不提供相應的宣告,則無法在以父類定義的物件中使用該方法。
(2) 我們在運用里氏代換原則時,儘量把父類設計為抽象類或者介面,讓子類繼承父類或實現父介面,並實現在父類中宣告的方法,執行時,子類例項替換父類例項,我們可以很方便地擴充套件系統的功能,同時無須修改原有子類的程式碼,增加新的功能可以透過增加一個新的子類來實現。里氏代換原則是開閉原則的具體實現手段之一。
(3) Java語言中,在編譯階段,Java編譯器會檢查一個程式是否符合里氏代換原則,這是一個與實現無關的、純語法意義上的檢查,但Java編譯器的檢查是有侷限的,里氏代換原則是實現開閉原則的重要方式之一。在本例項中,在傳遞引數時使用基類物件,除此以外,在定義成員變數、定義區域性變數、確定方法返回型別時都可使用里氏代換原則。針對基類程式設計,在程式執行時再確定具體子類。

四、依賴倒置原則(Dependence Inversion Principle,DIP)

定義:
高層模組不應該依賴低層模組,兩者都應該依賴其抽象;抽象不應該依賴細節,細節應該依賴抽象,其核心思想是:要面向介面程式設計,不要面向實現程式設計。
依賴倒轉原則要求我們在程式程式碼中傳遞引數時或在關聯關係中,儘量引用層次高的抽象層類,即使用介面和抽象類進行變數型別宣告、引數型別宣告、方法返回型別宣告,以及資料型別的轉換等,而不要用具體類來做這些事情。為了確保該原則的應用,一個具體類應當只實現介面或抽象類中宣告過的方法,而不要給出多餘的方法,否則將無法呼叫到在子類中增加的新方法。
在引入抽象層後,系統將具有很好的靈活性,在程式中儘量使用抽象層進行程式設計,而將具體類寫在配置檔案中,這樣一來,如果系統行為發生變化,只需要對抽象層進行擴充套件,並修改配置檔案,而無須修改原有系統的原始碼,在不修改的情況下來擴充套件系統的功能,滿足開閉原則的要求。
在實現依賴倒轉原則時,我們需要針對抽象層程式設計,而將具體類的物件透過依賴注入(DependencyInjection, DI)的方式注入到其他物件中,依賴注入是指當一個物件要與其他物件發生依賴關係時,透過抽象來注入所依賴的物件。常用的注入方式有三種,分別是:構造注入,設值注入(Setter注入)和介面注入。構造注入是指透過建構函式來傳入具體類的物件,設值注入是指透過Setter方法來傳入具體類的物件,而介面注入是指透過在介面中宣告的業務方法來傳入具體類的物件。這些方法在定義時使用的是抽象型別,在執行時再傳入具體型別的物件,由子類物件來覆蓋父類物件。
依賴倒置原則的作用
(1)依賴倒置原則可以降低類間的耦合性。
(2)依賴倒置原則可以提高系統的穩定性。
(3)依賴倒置原則可以減少並行開發引起的風險。
(4)依賴倒置原則可以提高程式碼的可讀性和可維護性。
依賴倒置原則的實現方法
依賴倒置原則的目的是透過要面向介面的程式設計來降低類間的耦合性,所以我們在實際程式設計中只要遵循以下4點,就能在專案中滿足這個規則。
(1)每個類儘量提供介面或抽象類,或者兩者都具備。
(2)變數的宣告型別儘量是介面或者是抽象類。
(3)任何類都不應該從具體類派生。
(4)使用繼承時儘量遵循里氏替換原則

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

定義:使用多個專門的介面,而不使用單一的總介面,即客戶端不應該依賴那些它不需要的介面。
根據介面隔離原則,當一個介面太大時,我們需要將它分割成一些更細小的介面,使用該介面的客戶端僅需知道與之相關的方法即可。每一個介面應該承擔一種相對獨立的角色,不幹不該乾的事,該乾的事都要幹。這裡的“介面”往往有兩種不同的含義:一種是指一個型別所具有的方法特徵的集合,僅僅是一種邏輯上的抽象;另外一種是指某種語言具體的“介面”定義,有嚴格的定義和結構,比如Java語言中的interface。對於這兩種不同的含義,ISP的表達方式以及含義都有所不同:
(1) 當把“介面”理解成一個型別所提供的所有方法特徵的集合的時候,這就是一種邏輯上的概念,介面的劃分將直接帶來型別的劃分。可以把介面理解成角色,一個介面只能代表一個角色,每個角色都有它特定的一個介面,此時,這個原則可以叫做“角色隔離原則”。
(2) 如果把“介面”理解成狹義的特定語言的介面,那麼ISP表達的意思是指介面僅僅提供客戶端需要的行為,客戶端不需要的行為則隱藏起來,應當為客戶端提供儘可能小的單獨的介面,而不要提供大的總介面。在物件導向程式語言中,實現一個介面就需要實現該介面中定義的所有方法,因此大的總介面使用起來不一定很方便,為了使介面的職責單一,需要將大介面中的方法根據其職責不同分別放在不同的小介面中,以確保每個介面使用起來都較為方便,並都承擔某一單一角色。介面應該儘量細化,同時介面中的方法應該儘量少,每個介面中只包含一個客戶端(如子模組或業務邏輯類)所需的方法即可,這種機制也稱為“定製服務”,即為不同的客戶端提供寬窄不同的介面。
介面隔離原則和單一職責都是為了提高類的內聚性、降低它們之間的耦合性,體現了封裝的思想,但兩者是不同的:
(1)單一職責原則注重的是職責,而介面隔離原則注重的是對介面依賴的隔離。
(2)單一職責原則主要是約束類,它針對的是程式中的實現和細節;介面隔離原則主要約束介面,主要針對抽象和程式整體框架的構建。
介面隔離原則的優點
介面隔離原則是為了約束介面、降低類對介面的依賴性,遵循介面隔離原則有以下 5 個優點。
(1)將臃腫龐大的介面分解為多個粒度小的介面,可以預防外來變更的擴散,提高系統的靈活性和可維護性。
(2)介面隔離提高了系統的內聚性,減少了對外互動,降低了系統的耦合性。
(3)如果介面的粒度大小定義合理,能夠保證系統的穩定性;但是,如果定義過小,則會造成介面數量過多,使設計複雜化;如果定義太大,靈活性降低,無法提供定製服務,給整體專案帶來無法預料的風險。
(4)使用多個專門的介面還能夠體現物件的層次,因為可以透過介面的繼承,實現對總介面的定義。
(5)能減少專案工程中的程式碼冗餘。過大的大介面裡面通常放置許多不用的方法,當實現這個介面的時候,被迫設計冗餘的程式碼。
介面隔離原則的實現方法
在具體應用介面隔離原則時,應該根據以下幾個規則來衡量。
(1)介面儘量小,但是要有限度。一個介面只服務於一個子模組或業務邏輯。
(2)為依賴介面的類定製服務。只提供呼叫者需要的方法,遮蔽不需要的方法。
(3)瞭解環境,拒絕盲從。每個專案或產品都有選定的環境因素,環境不同,介面拆分的標準就不同深入瞭解業務邏輯。
(4)提高內聚,減少對外互動。使介面用最少的方法去完成最多的事情。
在使用介面隔離原則時,我們需要注意控制介面的粒度,介面不能太小,如果太小會導致系統中介面氾濫,不利於維護;介面也不能太大,太大的介面將違背介面隔離原則,靈活性較差,使用起來很不方便。一般而言,介面中僅包含為某一類使用者定製的方法即可,不應該強迫客戶依賴於那些它們不用的方法。

六、迪米特法則(Law of Demeter, LoD)

定義:迪米特法則(Law of Demeter, LoD):一個軟體實體應當儘可能少地與其他實體發生相互作用。
迪米特法則(Law of Demeter,LoD)又叫作最少知識原則(Least Knowledge Principle,LKP),
迪米特法則的優點
迪米特法則要求限制軟體實體之間通訊的寬度和深度,正確使用迪米特法則將有以下兩個優點。
降低了類之間的耦合度,提高了模組的相對獨立性。
由於親合度降低,從而提高了類的可複用率和系統的擴充套件性。
但是,過度使用迪米特法則會使系統產生大量的中介類,從而增加系統的複雜性,使模組之間的通訊效率降低。所以,在釆用迪米特法則時需要反覆權衡,確保高內聚和低耦合的同時,保證系統的結構清晰
迪米特法則的實現方法
從迪米特法則的定義和特點可知,它強調以下兩點:
從依賴者的角度來說,只依賴應該依賴的物件。
從被依賴者的角度說,只暴露應該暴露的方法。
所以,在運用迪米特法則時要注意以下 6 點。
在類的劃分上,應該建立弱耦合的類。類與類之間的耦合越弱,就越有利於實現可複用的目標。
在類的結構設計上,儘量降低類成員的訪問許可權。
在類的設計上,優先考慮將一個類設定成不變類。
在對其他類的引用上,將引用其他物件的次數降到最低。
不暴露類的屬性成員,而應該提供相應的訪問器(set 和 get 方法)。
謹慎使用序列化(Serializable)功能。
總結
單一職責原則告訴我們實現類要職責單一
里氏替換原則告訴我們不要破壞繼承體系
依賴倒置原則告訴我們要面向介面程式設計
介面隔離原則告訴我們在設計介面的時候要精簡單一
迪米特原則告訴我們要降低耦合
開閉原則是總綱,告訴我們要對擴充套件開放,對修改關閉

相關文章