嘁,都2020了,你咋還在單純的使用if-else
在高級(jí)語言中,基本上都提供了像if-else
和switch-case
這樣的條件語句,方便大伙進(jìn)行判斷——引導(dǎo)程序走向。我們?cè)趯懗绦驎r(shí),常常需要指明兩條或者更多的執(zhí)行路徑,使得程序執(zhí)行時(shí),能夠選擇其中一條路徑,去執(zhí)行相應(yīng)的語句,產(chǎn)生對(duì)應(yīng)的結(jié)果 —— 這也是條件語句在程序中的作用。
if-else的例子
各位在初學(xué)C語言時(shí),應(yīng)該都寫過這樣一個(gè)程序:輸出每個(gè)月的天數(shù):
//C語言代碼片段 int Days(int months, int years){ int days; if(months==1 || months==3 || months==5 || months==7 || months==8 || months==10 || months==12){ days=31; }else if(months==2){ if((years%4==0 && years%100!=0) || years%400==0){ days=29; }else{ days=28; } }else if(months==4 || months==6 || months==9 || months==11){ days=30; }else{ printf("輸入錯(cuò)誤!請(qǐng)重新輸入:\n"); Days(months,years); } return days; }
這個(gè)程序雖是“耳熟能詳”的,但后來看著未免感覺有些【繁瑣】,多層if-else的嵌套不僅使得可讀性降低,還會(huì)大大影響程序運(yùn)行的效率。。。
if-else的問題
從上面就可以看出,if-else判斷語句使用起來非常簡(jiǎn)單,但是在稍微復(fù)雜的邏輯場(chǎng)景下,對(duì)if-else的頻繁使用(或說:濫用)就會(huì)容易導(dǎo)致整個(gè)項(xiàng)目的可讀性和可維護(hù)性大大降低。
我們可以試想一下,如果項(xiàng)目中出現(xiàn)了一種新的情況,那么我們要在原有的代碼基礎(chǔ)上繼續(xù)增加if-else。但是需求是不會(huì)減少的。這樣惡性循環(huán)下去,原本的幾個(gè)if-else可能在更新了幾個(gè)版本后變成了幾十個(gè),這可真是令人哭笑不得的事。
(當(dāng)然,現(xiàn)在也許你的公司會(huì)有硬性要求,或者開發(fā)模板,那就恭喜你了…)
從設(shè)計(jì)模式的角度考慮,if-else簡(jiǎn)直具有了“壞”代碼具有的一切:
- 數(shù)據(jù)和實(shí)現(xiàn)邏輯強(qiáng)耦合
- 擴(kuò)展麻煩,維護(hù)性低
改善if-else
if-else并非是需要全部被代替的,確切的說,我們現(xiàn)在只能去不斷的改善它,使他運(yùn)行的更為【流暢】。
短路符號(hào)和三元表達(dá)式
前幾天筆者還在群里說這兩個(gè):短路符號(hào),又叫“邏輯運(yùn)算符”,在一些簡(jiǎn)單的場(chǎng)景下,我們完全可以用它來代替if-else(尤其是那些需要“幾個(gè)條件同時(shí)滿足”的場(chǎng)景下):
比如這個(gè)——判斷一個(gè)數(shù)是不是2的冪:
//c++代碼片段 class Solution { public: bool isPowerOfTwo(int n) { //如果一個(gè)數(shù)是 2 的次方數(shù)的話,那么它的二進(jìn)數(shù)必然是最高位為1,其它都為 0 , //那么如果此時(shí)我們減 1 的話,則最高位會(huì)降一位,其余為 0 的位現(xiàn)在都為變?yōu)?1, //那么我們把兩數(shù)相與,就會(huì)得到 0 return (n > 0) && (!(n & (n - 1))); } };
我們也可以用三元符號(hào)來代替if-else,它是幾乎最合適的計(jì)算機(jī)判斷符號(hào)(筆者自認(rèn)為?。?,尤其適用于多條件復(fù)合判斷(一層嵌套一層)。不過需要注意的是,大量的三元運(yùn)算符卻容易影響代碼的可讀性:
比如——判斷 n! 結(jié)果尾數(shù)中零的數(shù)量:
//java代碼片段 public class Solution { public int trailingZeroes(int n) { //不斷遞歸 return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5); } }
當(dāng)然,我們還有一種改進(jìn)方法:如果每種條件下代碼邏輯比較多,也可以考慮提前跳出來結(jié)束函數(shù)——這是借鑒了for循環(huán)。
說說switch-case
switch-case是語言自身提供的另一種條件語句,它和if在本質(zhì)上并沒有什么區(qū)別,只是代碼看上去會(huì)更簡(jiǎn)潔。比如——判斷年齡:
goodswitch(age){ case 10: break; case 20: break; case 30: break; //... }
但是switch-case無法從根本上解決多個(gè)相似條件下需要多次重復(fù)的問題。
表驅(qū)動(dòng)法
這個(gè)是筆者最為推崇的一種寫法,它幾乎在大數(shù)據(jù)量判斷、范圍區(qū)別處理等問題上都有解決方案!
現(xiàn)在讓我們?cè)賮砜次恼麻_頭那道題:輸出每個(gè)月有多少天
我們不妨轉(zhuǎn)換一下思路,每個(gè)月份對(duì)應(yīng)一個(gè)數(shù)字,而月份都是按順序排列的,所以我們是否可以用一個(gè)數(shù)組來存儲(chǔ)天數(shù),然后用下標(biāo)來訪問?
//javascript 語法片段 const month=new Date().getMonth(), year=new Date().getFullYear(), isLeapYear=year%4==0 && year%100!=0 || year%400==0; const monthDays=[31,isLeapYear ? 29 : 28,31,30,31,30,31,31,30,31,30,31]; const days=monthDays[month];
哦,這個(gè)代碼運(yùn)行起來可簡(jiǎn)單多了——至少看起來是這樣。
還有上面判斷年齡的代碼,我們也可以這樣寫:
//JavaScript 語法片段 ages=[10,20,...]; funs=['a1','a2',...]; for(let i in ages){ if(age==ages[i]){ funs[i](); } } function a1(){ } function a2(){ } //...
看了兩個(gè)例子,想必你對(duì)【表驅(qū)動(dòng)法】有了了解:
表驅(qū)動(dòng)法就是一種編程模式,從表里面查找信息而不使用邏輯語句。事實(shí)上,凡是能通過邏輯語句來選擇的事物,都可以通過查表來選擇。對(duì)簡(jiǎn)單的情況而言,使用邏輯語句更為容易和直白。但隨著邏輯鏈的越來越復(fù)雜,查表法也就愈發(fā)顯得更具吸引力?!洞a大全》
使用表驅(qū)動(dòng)可不像if-else那樣“輕松”,我們需要先思考兩個(gè)問題:
如何從表中查詢數(shù)據(jù)?如果if-else判斷的是范圍,該怎么查?查什么?(數(shù)據(jù)?索引?)
基于這兩個(gè)問題,有人將依據(jù)表驅(qū)動(dòng)的查詢分為三種:
直接訪問索引訪問階梯訪問
1、直接訪問表
筆者最近按照母親的“旨意”跑了一趟保險(xiǎn)公司,發(fā)現(xiàn)這個(gè)保險(xiǎn)費(fèi)率非常麻煩——它會(huì)根據(jù)年齡、性別、婚姻狀態(tài)等不同情況變化??粗厦孑敵鋈掌诘某绦蛳胍幌拢绻阌眠壿嬁刂平鈽?gòu)(if or switch)來表示不同費(fèi)率,那會(huì)有多麻煩?。ㄊ聦?shí)上,你的代碼可能會(huì)像八爪魚一樣…)
我們能夠知道,這里的【年齡】是個(gè)范圍!沒法用數(shù)組或者對(duì)象來做映射。這有兩種解決方案:直接訪問表 or 階梯訪問表。筆者決定先試試“直接訪問表”的方式,并找到了兩種方法:
復(fù)制信息從而能夠直接使用鍵值:我們可以給 1-17 年齡范圍的每個(gè)年齡都復(fù)制一份信息,然后直接用 age 來訪問,同理對(duì)其他年齡段的也都一樣。這種方法在于操作很簡(jiǎn)單,表的結(jié)構(gòu)也很簡(jiǎn)單。但有個(gè)缺點(diǎn)就是會(huì)浪費(fèi)空間,畢竟生成了很多冗余信息。(不建議使用)轉(zhuǎn)換鍵值,如果我們把年齡范圍轉(zhuǎn)換成鍵呢?這樣就可以直接來訪問了,唯一需要考慮的問題就是有些情境下年齡如何轉(zhuǎn)換為鍵值。
對(duì)于第二種方法,有人可能疑惑了:還要用if-else轉(zhuǎn)換? 當(dāng)然。前面已經(jīng)說過:簡(jiǎn)單的if-else不會(huì)有什么問題的,表驅(qū)動(dòng)只是為了優(yōu)化復(fù)雜的邏輯判斷,使其更靈活、易擴(kuò)展。
//TypeScript 語法片段 const Age={ 0:"unadult", 1:"adult" } const Gender={ 0:"female", 1:"male" } const Marry={ 0:"unmarried", 1:"married" } const rateMap={ [Age[0]+Gender[0]+Marry[0]]:0.1, [Age[0]+Gender[0]+Marry[1]]:0.2, [Age[0]+Gender[1]+Marry[1]]:0.3, [Age[0]+Gender[1]+Marry[0]]:0.4, [Age[1]+Gender[0]+Marry[0]]:0.5, [Age[1]+Gender[0]+Marry[1]]:0.6, [Age[1]+Gender[1]+Marry[1]]:0.7, [Age[1]+Gender[1]+Marry[0]]:0.8 } const isAdult=(age:number)=>age>=18 ? 1: 0 const getDate=(age,hasMarried,gender)=>{ age=isAdult(age) return rateMap[Age[age]+Gender[gender]+Marry[marry]] }
這樣才是正確的打開方式嘛!
哦對(duì),剛剛好像還說了一種方法:
2、階梯訪問表
同樣是為了解決上面那個(gè)年齡范圍的問題,階梯訪問沒有索引訪問直接,但是會(huì)更節(jié)省空間。
為了使用階梯方法,你需要把每個(gè)區(qū)間的上限寫入一張表中,然后通過循環(huán)來檢查年齡所在的區(qū)間,所以在使用階梯訪問的時(shí)候一定要注意檢查區(qū)間的端點(diǎn)。
//TypeScript 語法片段 const ageRanges:number[]=[17,65,100], keys:string[]=['<18','18-65','>65']; const getKey=(age:number):string=>{ for(let i in keys){ //console.log(i); //console.log(ageRanges[i]); if(age<=ageRanges[i]){ return keys[i]; } } return keys[keys.length-1]; }
3、索引訪問表
實(shí)際中的保險(xiǎn)費(fèi)率問題,在處理年齡范圍的時(shí)候很頭疼,這種范圍往往不像上面第一種方法中那么容易得到 ‘key'。
我們當(dāng)時(shí)提到了復(fù)制信息從而能夠直接使用鍵值,但是這種方法浪費(fèi)了很多空間,因?yàn)槊總€(gè)年齡都會(huì)保存著一份數(shù)據(jù)。
但是如果我們只是保存索引,通過這個(gè)索引來查詢數(shù)據(jù)呢?
假設(shè)人剛出生是0歲,最多能活到 100 歲,那么我們需要?jiǎng)?chuàng)建一個(gè)長(zhǎng)度為 101 的數(shù)組,數(shù)組的下標(biāo)對(duì)應(yīng)著人的年齡,這樣在 0-17 的每個(gè)年齡我們都儲(chǔ)存 ‘<18',在18-65儲(chǔ)存 ‘18-65', 在65以上儲(chǔ)存 ‘>65'。這樣我們通過年齡就可以拿到對(duì)應(yīng)的索引,再通過索引來查詢對(duì)應(yīng)的數(shù)據(jù)。
看起來這種方法要比上面的直接訪問表更復(fù)雜,但是在一些很難通過轉(zhuǎn)換鍵值、數(shù)據(jù)占用空間很大的場(chǎng)景下可以試試通過索引來訪問:
//Typescript 代碼片段 const ages:string[]=['<18','<18','<18',...'18-65','18-65','18-65',...'>65','>65','>65',...'>65']; const ageKey:string=ages[age];
這樣雖然在造表的時(shí)候稍有些麻煩,但是在處理數(shù)據(jù)時(shí)卻是異常簡(jiǎn)便!
表驅(qū)動(dòng)的典型應(yīng)用
表驅(qū)動(dòng)最大的意義就是將條件判斷(數(shù)據(jù))和邏輯剝離分開,將條件用可配置的表(對(duì)象 or 數(shù)組)來管理
將0-360°劃分為8個(gè)不同的空間,但不要總是用if-else實(shí)現(xiàn):
//JavaScript 代碼片段 const keys=['A','B','C','D','E','F','G','H'], range=[45,90,135,180,225,270,315,360]; const degreeTkey=(rage)=>{ for(let i in range){ if(rage<=range[i]){ return keys[i]; } } } const map={ 'A':()=>{ //... }, 'B':()=>{ //... }, //... } //調(diào)用如: map[degreeTkey(46)]();
枚舉解決if-else對(duì)應(yīng)關(guān)系復(fù)雜的問題
啥角色干啥事,這是一個(gè)很明顯的對(duì)應(yīng)關(guān)系,所以學(xué)過的“枚舉”為啥不用?
其實(shí)枚舉和上面提到的【表搜索】很像:我們舉一個(gè)“系統(tǒng)管理員操作權(quán)限”的問題
首先定義一個(gè)公用接口 RoleOperation,表示不同角色所能做的操作:
public interface RoleOperation { String op();//表示某個(gè)角色可以做哪些op操作 }
接下來我們將不同角色的情況全部交由枚舉類來做,定義一個(gè)不同角色有不同權(quán)限的枚舉類 RoleEnum
:
public enum RoleEnum implements Role0peration { //系統(tǒng)管理員(有A操作權(quán)限) ROLE_ ROOT_ _ADMIN { @Override public String op() { return "ROLE_ ROOT_ ADMIN:" + " has AAA permission"; } }, //訂單管理員(有B操作權(quán)限) ROLE_ ORDER_ ADMIN { @override public String op() { return "ROLE_ ORDER_ _ADMIN:" + " has BBB permission"; } }, //普通用戶(有C操作權(quán)限) ROLE_ NORMAL { @Override public String op() { return "ROLE_ NORMAL:" + "has CCC permission"; } }; }
而且這樣一來,以后假如我想擴(kuò)充條件,只需要去枚舉類中加代碼即可,而不是去改以前的代碼,這豈不很穩(wěn)!
public class JudgeRole { public String judge( String roleName ) { //一行代碼搞定!之前的if/else沒了! return RoleEnum.va1ue0f(roleName).op(); } }
工廠模式解決if-else“分支過多”問題
不同分支做不同的事情,很明顯就提供了使用工廠模式的契機(jī),我們只需要將不同情況單獨(dú)定義好,然后去工廠類里面聚合即可。
首先,針對(duì)不同的角色,可以單獨(dú)定義其業(yè)務(wù)類:
//系統(tǒng)管理員(有A操作權(quán)限) public class RootAdminRole implements Role0peration { private String roleName ; public RootAdminRole( String roleName){ this.roleName = roleName ; } @Override public String op() { return roleName + "has AAA permission" ; } }
//訂單管理員(有B操作權(quán)限) public class OrderAdminRole implements RoleOperation { private String roleName ; public OrderAdminRole( String roleName ) { this.roleName = roleName ; } @Override public String op() { return roleName + "has BBB permission"; } }
//普通用戶(有C操作權(quán)限) public class NormalRole implements RoleOperation { private String roleName ; public NormalRole( String roleName){ this.roleName = roleName; } @Override public String op() { return roleName + "has CCC permission"; } }
接下來再寫一個(gè)工廠類 RoleFactory
對(duì)上面不同角色進(jìn)行聚合:
public class RoleFactory { static Map<String, Role0peration> roleOperationMap = new HashMap<>(); //在靜態(tài)塊中先把初始化工作全部做完 static { role0perationMap.put( "ROLE_ ROOT_ ADMIN", new RootAdminRole("ROLE_ _ROOT_ ADMIN") ) : roleOperationMap.put( "ROLE_ ORDER_ ADMIN", new OrderAdminRole("ROLE_ ORDER_ ADMIN") ); role0perationMap.put( "ROLE_ NORMAL", new NormalRole("ROLE_ NORMAL") ); } pub1ic static RoleOperation getOp( String roleName ) { return roleOperationMap.get( roleName ) ; } }
接下來借助上面這個(gè)工廠,業(yè)務(wù)代碼調(diào)用也只需一行代碼, if/else同樣被消除了:
public class JudgeRole { public String judge(String roleName){ //一行代碼搞定! 之前的if/else也沒了! return RoleFactory.get0p(roleName).op(); } }
這樣的話以后想擴(kuò)展條件也很容易,只需要增加新代碼,而不需要?jiǎng)右郧暗臉I(yè)務(wù)代碼,非常符合“開閉原則”。
到此這篇關(guān)于嘁,都2020了,你咋還在單純的使用if-else的文章就介紹到這了,更多相關(guān)if-else使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
fastjson到底做錯(cuò)了什么?為什么會(huì)被頻繁爆出漏洞?(推薦)
前段時(shí)間,fastjson被爆出過多次存在漏洞,很多文章報(bào)道了這件事兒,并且給出了升級(jí)建議。本文給大家分享fastjson的releaseNote以及部分源代碼。感興趣的朋友跟隨小編一起看看吧2020-07-07MobaXterm上傳下載文件、文件夾到服務(wù)器上的操作方法
這篇文章主要介紹了MobaXterm上傳下載文件、文件夾到服務(wù)器上的操作方法,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07使用Windows自帶的IIS服務(wù)搭建本地站點(diǎn)并遠(yuǎn)程訪問的操作方法
在Windows系統(tǒng)中實(shí)際上集成了建立網(wǎng)站所必須的軟件環(huán)境,今天就讓我們來看看,如何使用Windows自帶的網(wǎng)站程序建立網(wǎng)站吧,感興趣的朋友一起看看吧2023-12-12pycharm、idea、golang等JetBrains其他IDE修改行分隔符的詳細(xì)步驟(換行符)
這篇文章主要介紹了pycharm、idea、golang等JetBrains其他IDE修改行分隔符(換行符),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08gradle+shell實(shí)現(xiàn)自動(dòng)系統(tǒng)簽名
這篇文章主要介紹了gradle+shell實(shí)現(xiàn)自動(dòng)系統(tǒng)簽名的相關(guān)資料,需要的朋友可以參考下2019-08-08Flyway數(shù)據(jù)庫(kù)版本控制的教程詳解
這篇文章主要介紹了Flyway數(shù)據(jù)庫(kù)版本控制的教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2015-07-07