摘要:表驅動法就是一種編程模式,從表里面查找信息而不使用邏輯語句。但隨著邏輯鏈的越來越復雜,查表法也就愈發顯得更具吸引力。前面已經說過,簡單的是沒什么問題的,表驅動只是為了優化復雜的邏輯判斷,使其變得更靈活易擴展。
在我們平時的開發中,if else是最常用的條件判斷語句。在一些簡單的場景下,if else用起來很爽,但是在稍微復雜一點兒的邏輯中,大量的if else就會讓別人看的一臉蒙逼。
如果別人要修改或者新增一個條件,那就要在這個上面繼續增加條件。這樣惡性循環下去,原本只有幾個if else最后就有可能變成十幾個,甚至幾十個。
別說不可能,我就見過有人在React組件里面用了大量的if else,可讀性和可維護性非常差。(當然,這個不算if else的鍋,主要是組件設計的問題)
這篇文章主要參與自《代碼大全2》,原書中使用vb和java實現,這里我是基于TypeScript的實現,對書中內容加入了一些自己的理解。
從一個例子說起 日歷假如我們要做一個日歷組件,那我們肯定要知道一年12個月中每個月都多少天,這個我們要怎么判斷呢?
最笨的方法當然是用if else啊。
if (month === 1) { return 31; } if (month === 2) { return 28; } ... if (month === 12) { return 31; }
這樣一下子就要寫12次if,白白浪費了那么多時間,效率也很低。
這個時候就會有人想到用switch/case來做這個了,但是switch/case也不會比if簡化很多,依然要寫12個case啊!!!甚至如果還要考慮閏年呢?豈不是更麻煩?
我們不妨轉換一下思維,每個月份對應一個數字,月份都是按順序的,我們是否可以用一個數組來儲存天數?到時候用下標來訪問?
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];概念
看完上面的例子,相信你對表驅動法有了一定地認識。這里引用一下《代碼大全》中的總結。
表驅動法就是一種編程模式,從表里面查找信息而不使用邏輯語句。事實上,凡是能通過邏輯語句來選擇的事物,都可以通過查表來選擇。對簡單的情況而言,使用邏輯語句更為容易和直白。但隨著邏輯鏈的越來越復雜,查表法也就愈發顯得更具吸引力。
使用表驅動法前需要思考兩個問題,一個是如何從表中查詢,畢竟不是所有場景都像上面那么簡單的,如果if判斷的是不同的范圍,這該怎么查?
另一個則是你需要在表里面查詢什么,是數據?還是動作?亦或是索引?
基于這兩個問題,這里將查詢分為以下三種:
直接訪問
索引訪問
階梯訪問
直接訪問表我們上面介紹的那個日歷就是一個很好的直接訪問表的例子,但是很多情況并沒有這么簡單。
統計保險費率假設你在寫一個保險費率的程序,這個費率會根據年齡、性別、婚姻狀態等不同情況變化,如果你用邏輯控制結構(if、switch)來表示不同費率,那么會非常麻煩。
if (gender === "female") { if (hasMarried) { if (age < 18) { // } else if (age < 65) { // } else { // } } else if (age < 18) { // } else if (age < 65) { // } else if { // } } else { ... }
但是從上面的日歷例子來看,這個年齡卻是個范圍,不是個固定的值,沒法用數組或者對象來做映射,那么該怎么辦呢?這里涉及到了上面說的問題,如何從表中查詢?
這個問題可以用階梯訪問表和直接訪問表兩種方法來解決,階梯訪問這個后續會介紹,這里只說直接訪問表。
有兩種解決方法:
1、復制信息從而能夠直接使用鍵值
我們可以給1-17年齡范圍的每個年齡都復制一份信息,然后直接用age來訪問,同理對其他年齡段的也都一樣。這種方法在于操作很簡單,表的結構也很簡單。但有個缺點就是會浪費空間,畢竟生成了很多冗余信息。
2、轉換鍵值
我們不妨再換種思路,如果我們把年齡范圍轉換成鍵呢?這樣就可以直接來訪問了,唯一需要考慮的問題就是年齡如何轉換為鍵值。
我們當然可以繼續用if else完成這種轉換。前面已經說過,簡單的if else是沒什么問題的,表驅動只是為了優化復雜的邏輯判斷,使其變得更靈活、易擴展。
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] }索引訪問表
我們前面那個保險費率問題,在處理年齡范圍的時候很頭疼,這種范圍往往不像上面那么容易得到key。
我們當時提到了復制信息從而能夠直接使用鍵值,但是這種方法浪費了很多空間,因為每個年齡都會保存著一份數據,但是如果我們只是保存索引,通過這個索引來查詢數據呢?
假設人剛出生是0歲,最多能活到100歲,那么我們需要創建一個長度為101的數組,數組的下標對應著人的年齡,這樣在0-17的每個年齡我們都儲存"<18",在18-65儲存"18-65", 在65以上儲存">65"。
這樣我們通過年齡就可以拿到對應的索引,再通過索引來查詢對應的數據。
看起來這種方法要比上面的直接訪問表更復雜,但是在一些很難通過轉換鍵值、數據占用空間很大的場景下可以試試通過索引來訪問。
const ages: string[] = ["<18", "<18", "<18", "<18", ... , "18-65", "18-65", "18-65", "18-65", ... , ">65", ">65", ">65", ">65"] const ageKey: string = ages[age];階梯訪問表
同樣是為了解決上面那個年齡范圍的問題,階梯訪問沒有索引訪問直接,但是會更節省空間。
為了使用階梯方法,你需要把每個區間的上限寫入一張表中,然后通過循環來檢查年齡所在的區間,所以在使用階梯訪問的時候一定要注意檢查區間的端點。
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]; }
階梯訪問適合在索引訪問無法適用的場景,比如如果是浮點數,就無法用索引訪問創建一個數組來拿到索引。
在數據量比較大的情況下,考慮用二分查找來代替順序查找,。
在大多數情況下,優先使用直接訪問和索引訪問,除非兩者實在無法處理,才考慮使用階梯訪問。
從這三種訪問表來看,主要是為了解決如何從表中查詢,在不同的場景應該使用合適的訪問表。
參考資料:
代碼大全(第2版)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101251.html
摘要:常見例子如何優雅地輸出今天星期幾今天星期日一二三四五六上面的例子可以看到表就是一個字符串參考資料表驅動法數據驅動編程 前端中的if/else 在編寫業務代碼的時候,經常會出現條件判斷,如果判斷條件眾多,就會出現if/else天梯,如果新的業務場景出現,就需要再添加一個if/else,這樣的代碼維護起來,簡直是災難。 if (status === 0) { //do somethin...
摘要:支持字符串哈希列表集合有序集合等數據結構,目前不支持事務。是多入口以下關于表驅動法的描述,錯誤的是表驅動法可以作為復雜繼承結構的替代方案,難點在于一個經過深思熟慮的查詢表。表驅動法查找無規則分布的數據采用階梯訪問的方法最佳。 1、有關PHP字符串的說法,不對的是: CA、如果一個腳本的編碼是ISO-8859-1,則其中的字符串也會被編碼為 ISO-8859-1。B、PHP的字符串在內部...
摘要:遵循特定規則,利用操作符,終止節點和其他非終止節點,構造新的字符串非終結符是表示字符串的樹的內部節點。語法中的生產具有這種形式非終結符終結,非終結符和運算符的表達式語法的非終結點之一被指定為根。 大綱 基于狀態的構建 基于自動機的編程 設計模式:Memento提供了將對象恢復到之前狀態的功能(撤消)。 設計模式:狀態允許對象在其內部狀態改變時改變其行為。 表驅動結構* 基于語法的構...
閱讀 1206·2021-11-24 09:39
閱讀 2129·2021-11-22 13:54
閱讀 2111·2021-09-08 10:45
閱讀 1443·2021-08-09 13:43
閱讀 2985·2019-08-30 15:52
閱讀 3083·2019-08-29 15:38
閱讀 2848·2019-08-26 13:44
閱讀 3055·2019-08-26 13:30