摘要:表驅(qū)動(dòng)法就是一種編程模式,從表里面查找信息而不使用邏輯語(yǔ)句。但隨著邏輯鏈的越來(lái)越復(fù)雜,查表法也就愈發(fā)顯得更具吸引力。前面已經(jīng)說(shuō)過(guò),簡(jiǎn)單的是沒(méi)什么問(wèn)題的,表驅(qū)動(dòng)只是為了優(yōu)化復(fù)雜的邏輯判斷,使其變得更靈活易擴(kuò)展。
在我們平時(shí)的開發(fā)中,if else是最常用的條件判斷語(yǔ)句。在一些簡(jiǎn)單的場(chǎng)景下,if else用起來(lái)很爽,但是在稍微復(fù)雜一點(diǎn)兒的邏輯中,大量的if else就會(huì)讓別人看的一臉蒙逼。
如果別人要修改或者新增一個(gè)條件,那就要在這個(gè)上面繼續(xù)增加條件。這樣惡性循環(huán)下去,原本只有幾個(gè)if else最后就有可能變成十幾個(gè),甚至幾十個(gè)。
別說(shuō)不可能,我就見(jiàn)過(guò)有人在React組件里面用了大量的if else,可讀性和可維護(hù)性非常差。(當(dāng)然,這個(gè)不算if else的鍋,主要是組件設(shè)計(jì)的問(wèn)題)
這篇文章主要參與自《代碼大全2》,原書中使用vb和java實(shí)現(xiàn),這里我是基于TypeScript的實(shí)現(xiàn),對(duì)書中內(nèi)容加入了一些自己的理解。
從一個(gè)例子說(shuō)起 日歷假如我們要做一個(gè)日歷組件,那我們肯定要知道一年12個(gè)月中每個(gè)月都多少天,這個(gè)我們要怎么判斷呢?
最笨的方法當(dāng)然是用if else啊。
if (month === 1) { return 31; } if (month === 2) { return 28; } ... if (month === 12) { return 31; }
這樣一下子就要寫12次if,白白浪費(fèi)了那么多時(shí)間,效率也很低。
這個(gè)時(shí)候就會(huì)有人想到用switch/case來(lái)做這個(gè)了,但是switch/case也不會(huì)比if簡(jiǎn)化很多,依然要寫12個(gè)case啊?。?!甚至如果還要考慮閏年呢?豈不是更麻煩?
我們不妨轉(zhuǎn)換一下思維,每個(gè)月份對(duì)應(yīng)一個(gè)數(shù)字,月份都是按順序的,我們是否可以用一個(gè)數(shù)組來(lái)儲(chǔ)存天數(shù)?到時(shí)候用下標(biāo)來(lái)訪問(wèn)?
const month: number = new Date().getMonth(), year: number = new Date().getFullYear(), isLeapYear: boolean = year % 4 == 0 && year % 100 != 0 || year % 400 == 0; const monthDays: number[] = [31, isLeapYear ? 29 : 28, 31, ... , 31]; const days: number = monthDays[month];概念
看完上面的例子,相信你對(duì)表驅(qū)動(dòng)法有了一定地認(rèn)識(shí)。這里引用一下《代碼大全》中的總結(jié)。
表驅(qū)動(dòng)法就是一種編程模式,從表里面查找信息而不使用邏輯語(yǔ)句。事實(shí)上,凡是能通過(guò)邏輯語(yǔ)句來(lái)選擇的事物,都可以通過(guò)查表來(lái)選擇。對(duì)簡(jiǎn)單的情況而言,使用邏輯語(yǔ)句更為容易和直白。但隨著邏輯鏈的越來(lái)越復(fù)雜,查表法也就愈發(fā)顯得更具吸引力。
使用表驅(qū)動(dòng)法前需要思考兩個(gè)問(wèn)題,一個(gè)是如何從表中查詢,畢竟不是所有場(chǎng)景都像上面那么簡(jiǎn)單的,如果if判斷的是不同的范圍,這該怎么查?
另一個(gè)則是你需要在表里面查詢什么,是數(shù)據(jù)?還是動(dòng)作?亦或是索引?
基于這兩個(gè)問(wèn)題,這里將查詢分為以下三種:
直接訪問(wèn)
索引訪問(wèn)
階梯訪問(wèn)
直接訪問(wèn)表我們上面介紹的那個(gè)日歷就是一個(gè)很好的直接訪問(wèn)表的例子,但是很多情況并沒(méi)有這么簡(jiǎn)單。
統(tǒng)計(jì)保險(xiǎn)費(fèi)率假設(shè)你在寫一個(gè)保險(xiǎn)費(fèi)率的程序,這個(gè)費(fèi)率會(huì)根據(jù)年齡、性別、婚姻狀態(tài)等不同情況變化,如果你用邏輯控制結(jié)構(gòu)(if、switch)來(lái)表示不同費(fèi)率,那么會(huì)非常麻煩。
if (gender === "female") { if (hasMarried) { if (age < 18) { // } else if (age < 65) { // } else { // } } else if (age < 18) { // } else if (age < 65) { // } else if { // } } else { ... }
但是從上面的日歷例子來(lái)看,這個(gè)年齡卻是個(gè)范圍,不是個(gè)固定的值,沒(méi)法用數(shù)組或者對(duì)象來(lái)做映射,那么該怎么辦呢?這里涉及到了上面說(shuō)的問(wèn)題,如何從表中查詢?
這個(gè)問(wèn)題可以用階梯訪問(wèn)表和直接訪問(wèn)表兩種方法來(lái)解決,階梯訪問(wèn)這個(gè)后續(xù)會(huì)介紹,這里只說(shuō)直接訪問(wèn)表。
有兩種解決方法:
1、復(fù)制信息從而能夠直接使用鍵值
我們可以給1-17年齡范圍的每個(gè)年齡都復(fù)制一份信息,然后直接用age來(lái)訪問(wèn),同理對(duì)其他年齡段的也都一樣。這種方法在于操作很簡(jiǎn)單,表的結(jié)構(gòu)也很簡(jiǎn)單。但有個(gè)缺點(diǎn)就是會(huì)浪費(fèi)空間,畢竟生成了很多冗余信息。
2、轉(zhuǎn)換鍵值
我們不妨再換種思路,如果我們把年齡范圍轉(zhuǎn)換成鍵呢?這樣就可以直接來(lái)訪問(wèn)了,唯一需要考慮的問(wèn)題就是年齡如何轉(zhuǎn)換為鍵值。
我們當(dāng)然可以繼續(xù)用if else完成這種轉(zhuǎn)換。前面已經(jīng)說(shuō)過(guò),簡(jiǎn)單的if else是沒(méi)什么問(wèn)題的,表驅(qū)動(dòng)只是為了優(yōu)化復(fù)雜的邏輯判斷,使其變得更靈活、易擴(kuò)展。
enum genders { lessThan18 = "<18", between18And56 = "18-65", moreThan56 = ">65" } enum genders { female = 0, male = 1 } enum marry = { unmarried = 0, married = 1 } const age2key = (age: number): string => { if (age < 18) { return genders.lessThan18 } if (age < 65) { return genders.between18And56 } return genders.moreThan56 } const premiumRate: { [genders: string]: { [marry: string]: { rate: number } } } = { [genders.lessThan18]: { [genders.female]: { [marry.unmarried]: { rate: 0.1 }, [marry.married]: { rate: 0.2 } }, [genders.male]: { [marry.unmarried]: { rate: 0.3 }, [marry.married]: { rate: 0.4 } } }, [genders.between18And56]: { [genders.female]: { [marry.unmarried]: { rate: 0.5 }, [marry.married]: { rate: 0.6 } }, [genders.male]: { [marry.unmarried]: { rate: 0.7 }, [marry.married]: { rate: 0.8 } } }, [genders.moreThan56]: { [genders.female]: { [marry.unmarried]: { rate: 0.5 }, [marry.married]: { rate: 0.6 } }, [genders.male]: { [marry.unmarried]: { rate: 0.7 }, [marry.married]: { rate: 0.8 } } } const getRate = (age: number, hasMarried: 0 | 1, gender: 0 | 1) => { const ageKey: string = age2key(age); return premiumRate[ageKey] && premiumRate[ageKey][gender] && premiumRate[ageKey][gender][hasMarried] }索引訪問(wèn)表
我們前面那個(gè)保險(xiǎn)費(fèi)率問(wèn)題,在處理年齡范圍的時(shí)候很頭疼,這種范圍往往不像上面那么容易得到key。
我們當(dāng)時(shí)提到了復(fù)制信息從而能夠直接使用鍵值,但是這種方法浪費(fèi)了很多空間,因?yàn)槊總€(gè)年齡都會(huì)保存著一份數(shù)據(jù),但是如果我們只是保存索引,通過(guò)這個(gè)索引來(lái)查詢數(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"。
這樣我們通過(guò)年齡就可以拿到對(duì)應(yīng)的索引,再通過(guò)索引來(lái)查詢對(duì)應(yīng)的數(shù)據(jù)。
看起來(lái)這種方法要比上面的直接訪問(wèn)表更復(fù)雜,但是在一些很難通過(guò)轉(zhuǎn)換鍵值、數(shù)據(jù)占用空間很大的場(chǎng)景下可以試試通過(guò)索引來(lái)訪問(wèn)。
const ages: string[] = ["<18", "<18", "<18", "<18", ... , "18-65", "18-65", "18-65", "18-65", ... , ">65", ">65", ">65", ">65"] const ageKey: string = ages[age];階梯訪問(wèn)表
同樣是為了解決上面那個(gè)年齡范圍的問(wèn)題,階梯訪問(wèn)沒(méi)有索引訪問(wèn)直接,但是會(huì)更節(jié)省空間。
為了使用階梯方法,你需要把每個(gè)區(qū)間的上限寫入一張表中,然后通過(guò)循環(huán)來(lái)檢查年齡所在的區(qū)間,所以在使用階梯訪問(wèn)的時(shí)候一定要注意檢查區(qū)間的端點(diǎn)。
const ageRanges: number[] = [17, 65, 100], keys: string[] = ["<18", "18-65", ">65"], len: number = keys.length; const getKey = (age: number): string => { for (let i = 0; i < len; i++) { console.log("i", i) console.log("ageRanges", ageRanges[i]) if (age <= ageRanges[i]) { return keys[i] } } return keys[len-1]; }
階梯訪問(wèn)適合在索引訪問(wèn)無(wú)法適用的場(chǎng)景,比如如果是浮點(diǎn)數(shù),就無(wú)法用索引訪問(wèn)創(chuàng)建一個(gè)數(shù)組來(lái)拿到索引。
在數(shù)據(jù)量比較大的情況下,考慮用二分查找來(lái)代替順序查找,。
在大多數(shù)情況下,優(yōu)先使用直接訪問(wèn)和索引訪問(wèn),除非兩者實(shí)在無(wú)法處理,才考慮使用階梯訪問(wèn)。
從這三種訪問(wèn)表來(lái)看,主要是為了解決如何從表中查詢,在不同的場(chǎng)景應(yīng)該使用合適的訪問(wèn)表。
參考資料:
代碼大全(第2版)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/101251.html
摘要:常見(jiàn)例子如何優(yōu)雅地輸出今天星期幾今天星期日一二三四五六上面的例子可以看到表就是一個(gè)字符串參考資料表驅(qū)動(dòng)法數(shù)據(jù)驅(qū)動(dòng)編程 前端中的if/else 在編寫業(yè)務(wù)代碼的時(shí)候,經(jīng)常會(huì)出現(xiàn)條件判斷,如果判斷條件眾多,就會(huì)出現(xiàn)if/else天梯,如果新的業(yè)務(wù)場(chǎng)景出現(xiàn),就需要再添加一個(gè)if/else,這樣的代碼維護(hù)起來(lái),簡(jiǎn)直是災(zāi)難。 if (status === 0) { //do somethin...
測(cè)試基本知識(shí)1、請(qǐng)你分別介紹一下單元測(cè)試、集成測(cè)試、系統(tǒng)測(cè)試、驗(yàn)收測(cè)試、回歸測(cè)試:(1)單元測(cè)試:完成最小的軟件設(shè)計(jì)單元(模塊)的驗(yàn)證工作,目標(biāo)是確保模塊被正確的編碼,使用過(guò)程設(shè)計(jì)描述作為指南,對(duì)重要的控制路徑進(jìn)行測(cè)試以發(fā)現(xiàn)模塊內(nèi)的錯(cuò)誤,通常情況下是白盒的,對(duì)代碼風(fēng)格和規(guī)則、程序設(shè)計(jì)和結(jié)構(gòu)、業(yè)務(wù)邏輯等進(jìn)行靜態(tài)測(cè)試,及早的發(fā)現(xiàn)和解決不易顯現(xiàn)的錯(cuò)誤。(2)集成測(cè)試:通過(guò)測(cè)試發(fā)現(xiàn)與模塊接口有關(guān)的問(wèn)題。目...
摘要:靜態(tài)測(cè)試包括對(duì)于代碼測(cè)試,主要是測(cè)試代碼是否符合相應(yīng)的標(biāo)準(zhǔn)和規(guī)范。集成測(cè)試,是單元測(cè)試的下一階段,是指將通過(guò)測(cè)試的單元模塊組裝成系統(tǒng)或子系統(tǒng),再進(jìn)行測(cè)試,重點(diǎn)測(cè)試不同模塊的接口部門。 ...
摘要:支持字符串哈希列表集合有序集合等數(shù)據(jù)結(jié)構(gòu),目前不支持事務(wù)。是多入口以下關(guān)于表驅(qū)動(dòng)法的描述,錯(cuò)誤的是表驅(qū)動(dòng)法可以作為復(fù)雜繼承結(jié)構(gòu)的替代方案,難點(diǎn)在于一個(gè)經(jīng)過(guò)深思熟慮的查詢表。表驅(qū)動(dòng)法查找無(wú)規(guī)則分布的數(shù)據(jù)采用階梯訪問(wèn)的方法最佳。 1、有關(guān)PHP字符串的說(shuō)法,不對(duì)的是: CA、如果一個(gè)腳本的編碼是ISO-8859-1,則其中的字符串也會(huì)被編碼為 ISO-8859-1。B、PHP的字符串在內(nèi)部...
摘要:遵循特定規(guī)則,利用操作符,終止節(jié)點(diǎn)和其他非終止節(jié)點(diǎn),構(gòu)造新的字符串非終結(jié)符是表示字符串的樹的內(nèi)部節(jié)點(diǎn)。語(yǔ)法中的生產(chǎn)具有這種形式非終結(jié)符終結(jié),非終結(jié)符和運(yùn)算符的表達(dá)式語(yǔ)法的非終結(jié)點(diǎn)之一被指定為根。 大綱 基于狀態(tài)的構(gòu)建 基于自動(dòng)機(jī)的編程 設(shè)計(jì)模式:Memento提供了將對(duì)象恢復(fù)到之前狀態(tài)的功能(撤消)。 設(shè)計(jì)模式:狀態(tài)允許對(duì)象在其內(nèi)部狀態(tài)改變時(shí)改變其行為。 表驅(qū)動(dòng)結(jié)構(gòu)* 基于語(yǔ)法的構(gòu)...
閱讀 1288·2021-11-24 09:39
閱讀 2212·2021-11-22 13:54
閱讀 2256·2021-09-08 10:45
閱讀 1526·2021-08-09 13:43
閱讀 3042·2019-08-30 15:52
閱讀 3169·2019-08-29 15:38
閱讀 2904·2019-08-26 13:44
閱讀 3117·2019-08-26 13:30