摘要:在面向?qū)ο笤O(shè)計中,可維護性的復(fù)用是以設(shè)計原則為基礎(chǔ)的。面向?qū)ο笤O(shè)計原則為支持可維護性復(fù)用而誕生,這些原則蘊含在很多設(shè)計模式中,它們是從許多設(shè)計方案中總結(jié)出的指導(dǎo)性原則。
面向?qū)ο笤O(shè)計原則 概述
對于面向?qū)ο筌浖到y(tǒng)的設(shè)計而言,在支持可維護性的同時,提高系統(tǒng)的可復(fù)用性是一個至關(guān)重要的問題,如何同時提高一個軟件系統(tǒng)的可維護性和可復(fù)用性是面向?qū)ο笤O(shè)計需要解決的核心問題之一。在面向?qū)ο笤O(shè)計中,可維護性的復(fù)用是以設(shè)計原則為基礎(chǔ)的。每一個原則都蘊含一些面向?qū)ο笤O(shè)計的思想,可以從不同的角度提升一個軟件結(jié)構(gòu)的設(shè)計水平。 面向?qū)ο笤O(shè)計原則為支持可維護性復(fù)用而誕生,這些原則蘊含在很多設(shè)計模式中,它們是從許多設(shè)計方案中總結(jié)出的指導(dǎo)性原則。面向?qū)ο笤O(shè)計原則也是我們用于評價一個設(shè)計模式的使用效果的重要指標之一,在設(shè)計模式的學(xué)習(xí)中,大家經(jīng)常會看到諸如“XXX模式符合XXX原則”、“XXX模式違反了XXX原則”這樣的語句。
最常見的7種面向?qū)ο笤O(shè)計原則如下表所示:
一個類只負責(zé)一個功能領(lǐng)域中的相應(yīng)職責(zé),或者可以定義為:就一個類而言,應(yīng)該只有一個引起它變化的原因
從定義中不難思考,一個類的所做的事情越多,也就越難以復(fù)用,因為一旦做的事情多了,職責(zé)的耦合度就變高了所以我們根據(jù)這個原則應(yīng)該將不同職責(zé)封裝在不同類中,不同的變化封裝在不同類中。從我們平常的開發(fā)中不難發(fā)現(xiàn),如果一個類或者方法接口等等只做一件事,那么可讀性很高,并且復(fù)用性也很高,并且一旦需求變化,也容易維護,假如你一個類糅雜多個職責(zé),那么很難維護。
單一職責(zé)舉例分析從實際業(yè)務(wù)來剝離一個例子:現(xiàn)在有這么一種情況,某租車平臺個人模塊類涉及多個方法,有如下登錄、注冊、支付寶押金支付、微信押金支付、支付寶套餐支付、微信套餐支付、整個結(jié)構(gòu)如下:
/** * 個人模塊 */ @Controller public class userController{ /** * 登錄 */ public void login(){ } /** * 注冊 */ public void register(){ } /** * 押金支付(阿里) */ public void payAliDeposit(){ } /** * 押金支付(微信) */ public void payWXDeposit(){ } /** * 套餐支付(阿里) */ public void payAliPackage(){ } /** * 套餐支付(微信) */ public void payWXPackage(){ } }
我們可以看到很多功能都糅雜在一起,一個類做了那么多事情,很臃腫,別提維護,就連找代碼都很困難,所以我們可以對這個UserController進行拆解,與此同時我們應(yīng)該分包,比如這個應(yīng)該在xxx.xxx.userMoudule下面,可能支付相關(guān)的有公共的方法,登錄抑或也有公共的方法,那邊抽成公共服務(wù)去調(diào)用。
public class LoginController(){} public class registerController(){} public class depositPayController(){ // 支付寶支付 // 微信支付 } public class packagePayController(){ // 支付寶支付 // 微信支付 }
整個方案實現(xiàn)的目的就是為了解決高耦合,代碼復(fù)用率低下的問題。單一職責(zé)理解起來不難,但是實際操作需要根據(jù)具體業(yè)務(wù)的糅雜度來切割,實際上很難運用。
2.開閉原則 開閉原則簡介開閉原則是面向?qū)ο蟮目蓮?fù)用設(shè)計的第一塊基石,它是最重要的面向?qū)ο笤O(shè)計原則,定義如下:
一個軟件實體應(yīng)當(dāng)對擴展開放,對修改關(guān)閉。即軟件實體應(yīng)盡量在不修改原有代碼的情況下進行擴展。
軟件實體包括以下幾個部分:
項目或軟件產(chǎn)品中按照一定的邏輯規(guī)則劃分的模塊
抽象和類
方法
注意:開閉原則是指對擴展開放,對修改關(guān)閉,并不是說不做任何的修改。
開閉原則的優(yōu)勢可以使原來的測試代碼依舊可以運行,只需要對擴展的代碼進行測試即可
可以提高代碼的復(fù)用性
可以提高系統(tǒng)的維護性
如何使用開閉原則
抽象約束
通過接口或者抽象類約束擴展,對擴展進行邊界限定,不允許出現(xiàn)在接口或抽象類中不存在的public方法;
參數(shù)類型、引用對象盡量使用接口或者抽象類,而不是實現(xiàn)類;(針對抽象編程)
抽象層盡量保持穩(wěn)定,一旦確定即不允許修改。
元數(shù)據(jù)控制模塊行為
通俗來說就是通過配置文件來操作數(shù)據(jù),spring的控制反轉(zhuǎn)就是一個很典型的例子。
約定優(yōu)于配置
封裝變化
將相同的變化封裝到一個接口或者類中
將不同的變化封裝到不同的類或者接口中(單一職責(zé)的體現(xiàn))
案例某公司開發(fā)的租車系統(tǒng)有一個押金支付功能,支付方式有支付寶、阿里支付,后期可能還有銀聯(lián)支付、易支付等等,原始的設(shè)計方案如下:
// 客戶端調(diào)用-押金支付選擇支付手段 public class DepositPay { void pay(String type){ if(type.equals("ali")){ AliPay aliPay = new AliPay(); aliPay.pay(); }else if(type.equals("wx")){ WXPay wxPay = new WXPay(); wxPay.pay(); } } } // 支付寶支付 public class AliPay { public void pay() { System.out.println("正在使用支付寶支付"); } } // 微信支付 public class WXPay{ public void pay() { System.out.println("正在使用微信支付"); } }
在以上代碼中,如果需要增加銀聯(lián)支付,如YLPay,那么就必須要修改DepositPay中的pay方法的源代碼,增加新的判斷邏輯,違反了開閉原則(對修改關(guān)閉,對擴展開放,注意這邊的銀聯(lián)支付相當(dāng)于擴展,所以它沒有違反規(guī)則),所以現(xiàn)在必須重構(gòu)此代碼,讓其遵循開閉原則,做法如下:
增加一個接口,使得各種具體支付實現(xiàn)其接口
DepositPay類針對接口編程,由客戶端來決定具體使用哪種支付方式
重構(gòu)后的圖如下所示:
在上圖中我們引入了接口Pay,定義了pay方法,并且DepositPay是針對接口編程,通過setPayMode()由客戶端來實例化具體的支付方式,在DepositPay的pay()方法中調(diào)用payMode對象來支付。如果需要增加新的支付方式,比如銀聯(lián)支付,只需要讓它也實現(xiàn)Pay接口,在配置文件中配置銀聯(lián)支付即可,依賴注入是實現(xiàn)此開閉原則的一種手段,在這里不贅述,源碼如下:
public interface Pay { // 支付 void pay(); } public class AliPay implements Pay { @Override public void pay() { System.out.println("正在使用支付寶支付"); } } public class WXPay implements Pay{ @Override public void pay() { System.out.println("正在使用微信支付"); } } // 客戶端調(diào)用-押金支付選擇支付手段 public class DepositPay { // 支付方式 (這邊可以通過依賴注入的方式來注入) // 支付方式可以寫在配置文件中 // 現(xiàn)在不管你選用何種方式,我都不需要更改 @Autowired Pay payMode; void pay(Pay payMode){ payMode.pay(); } }
因為配置文件可以直接編輯,且不需要編譯,所以一般不認為更改配置文件是更改源碼。如果一個系統(tǒng)能做到只需要修改配置文件,無需修改源碼,那么復(fù)合開閉原則。
3.里氏代換原則 里氏替換原則簡介Barbara Liskov提出:標準定義:如果對每一個類型為S的對象o1,都有類型為T的對象o2,使得以T定義的所有程序P在所有的對象o1代換o2時,程序P的行為沒有變化,那么類型S是類型T的子類型。
上面的定義可能比較難以理解,簡單理解就是所有引用基類(父類的)地方都可以用子類來替換,且程序不會有任何的異常。但是反過來就不行,所有使用子類的地方則不一定能用基類來替代,很簡單的例子狗是動物,不能說動物是狗,因為可能還有貓。。。。
里氏替換原則是實現(xiàn)開閉原則的重要方式之一,由于使用基類的所有地方都可以用子類來替換,因此在程序中盡量使用基類來定義對象,在運行時確定其子類類型。
里氏替換原則約束子類必須實現(xiàn)父類的抽象方法,但不得重寫(覆蓋)父類的非抽象(已實現(xiàn))方法。
子類中可以添加特有方法(父類中不存在),此時則無法在以父類定義的對象中使用該方法,除非在使用的時候強轉(zhuǎn)基類成子類進行調(diào)用。
當(dāng)子類覆蓋或?qū)崿F(xiàn)父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松。
當(dāng)子類的方法實現(xiàn)父類的抽象方法時,方法的后置條件(即方法的返回值)要比父類更嚴格。
所以我們在運用里氏替換原則的時候,盡量把父類設(shè)計為抽象類或者接口,讓子類繼承父類或者實現(xiàn)接口并實現(xiàn)在父類中聲明的方法,運行時,子類實例替換父類實例,我們可以很方便地擴展系統(tǒng)的功能,同時無須修改原有子類的代碼,增加新的功能可以通過增加一個新的子類來實現(xiàn)。里氏代換原則是開閉原則的具體實現(xiàn)手段之一。里氏替換原則實戰(zhàn)
某租車系統(tǒng)客戶分為普通用戶(customer)和VIP客戶(VIPCustomer),系統(tǒng)需要提供一個根據(jù)郵箱重置密碼的功能。原始設(shè)計圖:
在編寫重置密碼的時候發(fā)現(xiàn),業(yè)務(wù)邏輯是一樣的,存在著大量的重復(fù)代碼,而且還可能增加新的用戶類型,為了減少代碼重復(fù)性,使用里氏替換原則進行重構(gòu):
圖上重置密碼交由ResetPassword類去處理,只需要傳入Customer類即可,不管任何類型的Customer類,只要繼承自Customer,都可以使用里氏替換原則進行替換,假如有新的類型,我們只需要在配置文件中注入新的類型即可。代碼如下(簡單意會一下):
// 抽象基類 public abstract class Customer { } public class CommonCustomer extends Customer{ } public class VIPCustomer extends Customer{ } // 重置密碼邏輯在這里實現(xiàn),只需要傳入對應(yīng)的類型即可 public class ResetPassword { void resetPassword(Customer customer){ } }
里氏替換原則是實現(xiàn)開閉原則不可或缺的手段之一,在本例中,通過傳遞參數(shù)使用基類對象,針對抽象編程,從而滿足開閉原則。
4.依賴倒轉(zhuǎn)原則 依賴倒轉(zhuǎn)原則簡介依賴倒轉(zhuǎn)原則(Dependency Inversion Principle, DIP):抽象不應(yīng)該依賴于細節(jié),細節(jié)應(yīng)當(dāng)依賴于抽象。換言之,要針對接口編程,而不是針對實現(xiàn)編程。
可以通俗的定義為兩種:
高層次的模塊不應(yīng)該依賴于低層次的模塊,他們都應(yīng)該依賴于抽象。
抽象不應(yīng)該依賴于具體實現(xiàn),具體實現(xiàn)應(yīng)該依賴于抽象。
要求我們在設(shè)計程序的時候盡量使用層次高的抽象層類,即使用接口和抽象類進行變量的聲明、參數(shù)類型聲明、方法返回類型聲明以及數(shù)據(jù)類型轉(zhuǎn)換等等,同時要注意一個具體類應(yīng)該只實現(xiàn)抽象類或者接口中存在的方法,不要給出多余的方法,這樣抽象類將無法調(diào)用子類增加的方法.我們可以通過配置文件來寫入具體類,這樣一旦程序行為改變,可直接改變配置文件,而不需要更改程序,重新編譯,通過依賴倒轉(zhuǎn)原則來滿足開閉原則。
在實現(xiàn)依賴倒轉(zhuǎn)原則時,我們需要針對抽象層編程,而將具體類的對象通過依賴注入(DependencyInjection, DI)的方式注入到其他對象中,依賴注入是指當(dāng)一個對象要與其他對象發(fā)生依賴關(guān)系時,通過抽象來注入所依賴的對象。常用的注入方式有三種,分別是:構(gòu)造注入,設(shè)值注入(Setter注入)和接口注入
依賴倒轉(zhuǎn)原則實例這部分可以參照上面開閉原則案例,可以從那例子中看出,開閉原則,依賴倒轉(zhuǎn)原則,里氏替換原則同時出現(xiàn)了,可以說`開閉原則是我們要實現(xiàn)的目標,而里氏替換原則是實現(xiàn)手段之一,而同時里氏替換原則又是依賴倒轉(zhuǎn)原則實現(xiàn)的基礎(chǔ),因為加入沒有這個理論,依賴倒轉(zhuǎn)原則是不成立的,無法針對抽象編程,要注意這3個原則基本都是同時出現(xiàn)的。
5.接口隔離原則 接口隔離原則簡介接口隔離原則的兩個定義:1:使用多個專門的接口,而不使用單一的總接口,即客戶端不應(yīng)該依賴那些它不需要的接口
2:類間的依賴關(guān)系應(yīng)該建立在最小的接口上
接口的含義:
一個接口代表一個角色,不應(yīng)該將不同的角色都交給一個接口,因為這樣可能會形成一個臃腫的大接口;
特定語言的接口,表示接口僅僅是提供客戶端需要的行為,客戶端不需要的行為則隱藏起來,應(yīng)當(dāng)為客戶端提供盡可能小的多帶帶的接口,而不要提供大的總接口。
根據(jù)接口隔離原則,我們可明白,每個接口都應(yīng)只承擔(dān)一種相對獨立的角色,不干不該干的事情.
實例演示場景:模擬動物平時的動作,當(dāng)然也包括人,最初的設(shè)計就是一個總接口IAnimal,里面定義動物會有的一些動作。
代碼如下:
public interface IAnimal{ /** * 吃飯 */ void eat(); /** * 工作 */ void work(); /** * 飛行 */ void fly(); } public class Tony implements IAnimal{ @Override public void eat() { System.out.println("tony吃"); } @Override public void work() { System.out.println("tony工作"); } @Override public void fly() { System.out.println("tony不會飛"); } } public class Bird implements IAnimal{ @Override public void eat() { System.out.println("鳥吃"); } @Override public void work() { System.out.println("鳥工作"); } @Override public void fly() { System.out.println("鳥飛"); } }
根據(jù)上面的寫法發(fā)現(xiàn)Tony需要實現(xiàn)飛的接口,這很明顯不僅僅是多余,而且不合理,因此需要通過接口隔離原則進行重構(gòu):
/** * 抽象動物的行為 */ public interface IAnimal { /** * 吃飯 */ void eat(); /** * 睡覺 */ void sleep(); } /** * 高級動物人 的行為 */ public interface IAdvancedAnimalBehavior { /** * 打牌 */ void playCard(); /** * 騎車 */ void byBike(); } /** * 低級動物的行為 */ public interface IJuniorAnimalBehavior { /** * fly */ void fly(); } /** * 實現(xiàn)高級動物人的共通方法 */ public class AbstractAdvancedAnimal implements IAnimal { @Override public void eat() { System.out.println("人吃"); } @Override public void sleep() { System.out.println("人睡"); } } /** * 實現(xiàn)低級動物人的共通方法 */ public class AbstractJuniorAnimal implements IAnimal { @Override public void eat() { System.out.println("動物吃"); } @Override public void sleep() { System.out.println("動物睡"); } } // tony public class Tony extends AbstractAdvancedAnimal implements IAdvancedAnimalBehavior { @Override public void playCard() { System.out.println("tony打牌"); } @Override public void byBike() { System.out.println("tony騎車"); } } // 鳥 public class Bird extends AbstractJuniorAnimal implements IJuniorAnimalBehavior{ @Override public void fly() { System.out.println("鳥飛"); } }
重構(gòu)之后,首先定義了一個總的動物接口的大類,然后分別使用了兩個抽象類(一個是高級動物,一個是低級動物)分別去實現(xiàn)這些公共的方法,實現(xiàn)中可以拋出異常,表明繼承此抽象類的類可以選擇性的重寫,可不重寫。之后再定義了兩個行為接口表明高級動物和低級動物所特有的,這樣使得接口之間完全隔離,動物接口不再糅雜各種各樣的角色,當(dāng)然接口的大小尺度還是要靠經(jīng)驗來調(diào)整,不能太小,會造成接口泛濫,也不能太大,會背離接口隔離原則。
6.合成復(fù)用原則 合成復(fù)用原則簡介合成復(fù)用原則(Composite Reuse Principle, CRP):盡量使用對象組合,而不是繼承來達到復(fù)用的目的。
通過合成復(fù)用原則來使一些已有的對象使之成為對象的一部分,一般通過組合/聚合關(guān)系來實現(xiàn),而盡量不要使用繼承。因為組合和聚合可以降低類之間的耦合度,而繼承會讓系統(tǒng)更加復(fù)雜,最重要的一點會破壞系統(tǒng)的封裝性,因為繼承會把基類的實現(xiàn)細節(jié)暴露給子類,同時如果基類變化,子類也必須跟著改變,而且耦合度會很高。
7.迪米特法則參考:https://www.cnblogs.com/muzon...
參考:https://blog.csdn.net/lovelio...
參考:https://blog.csdn.net/qq_3496...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/72715.html
摘要:所有即使是公司制定了最嚴格的代碼安全規(guī)范同時每個程序員都有能并完美執(zhí)行了安全編碼規(guī)范,也只能解決最多的潛在問題。大型為了確保安全還可以采用滲透測試和分析公司提出來的一項新興技術(shù),稱運行應(yīng)用程序自我保護功能。 盡管像銀行、大型電商以及政府等大型機構(gòu)在確保程序員寫出最安全的軟件上付出了巨大的努力:比如雇傭最有經(jīng)驗的程序員,使用昂貴的代碼分析工具等等。但是媒體頭條上還是經(jīng)??梢钥吹酱笮徒M織出...
概念:設(shè)計模式(Design Pattern)---人們在面對同類型軟件工程設(shè)計問題所總結(jié)的經(jīng)驗。模式不是代碼,而是某類問題的通用設(shè)計方案。目的:為了代碼的重用性、讓代碼易于理解,保證代碼的可靠性。分類:總體可以分為三大類:設(shè)計模式的5+2大原則:1,單一責(zé)任原則(Single Responsibility Principle)2,開放封閉原則(Open Closed Principle)3,里氏...
摘要:搞論文準備答辯的時候,仔細思考以及仔細閱讀很多設(shè)計模式的文章后,終于對開閉原則有了一點認識。其實,我們遵循設(shè)計模式其他幾大原則,以及使用種設(shè)計模式的目的就是遵循開閉原則。 之前簡單介紹了常見設(shè)計模式遵循的設(shè)計原則--單一職責(zé)原則,這篇介紹一下另外一個相當(dāng)重要和具有指導(dǎo)性的一個原則,開放關(guān)閉原則。但是,關(guān)于這一個原則的使用,經(jīng)驗是相當(dāng)重要的一個因素。 但是個人感覺開閉原則可能是設(shè)計模式幾...
摘要:正則表達式作為一個匹配的模板,是由定界符,原子普通字符,例如有特殊功能的字符稱為元字符,例如等以及模式修正符等部分組成的文字模式。正則表達式中可以使用編碼。限定符限定符用來指定正則表達式的一個給定原子必須要出現(xiàn)多少次才能滿足匹配。 正則表達式的定義 正則表達式就是描述字符排列模式的一種自定義的語法規(guī)則。由于正則表達式本身具有一套非常完整的、可以編寫模式的語法體系,提供了一種靈活且直觀的...
摘要:做前端開發(fā)已經(jīng)好幾年了,對設(shè)計模式一直沒有深入學(xué)習(xí)總結(jié)過。今天第一天,首先來講策略模式。什么是策略模式四兄弟的經(jīng)典設(shè)計模式中,對策略模式的定義如下定義一系列的算法,把它們一個個封裝起來,并且使它們可互相替換。 做前端開發(fā)已經(jīng)好幾年了,對設(shè)計模式一直沒有深入學(xué)習(xí)總結(jié)過。隨著架構(gòu)相關(guān)的工作越來越多,越來越能感覺到設(shè)計模式成為了我前進道路上的一個阻礙。所以從今天開始深入學(xué)習(xí)和總結(jié)經(jīng)典的設(shè)計模...
閱讀 718·2021-11-16 11:44
閱讀 3551·2019-08-26 12:13
閱讀 3246·2019-08-26 10:46
閱讀 2362·2019-08-23 12:37
閱讀 1193·2019-08-22 18:30
閱讀 2537·2019-08-22 17:30
閱讀 1843·2019-08-22 17:26
閱讀 2295·2019-08-22 16:20