表驅(qū)動法,邏輯控制優(yōu)化利器
本文轉(zhuǎn)載自微信公眾號「架構(gòu)精進之路」,作者張張。轉(zhuǎn)載本文請聯(lián)系架構(gòu)精進之路公眾號。
最近好多同學在開發(fā)過程中談到設(shè)計表結(jié)構(gòu)的一些idea,為了讓大家少走一些彎路,今天就計劃聊聊表驅(qū)動法吧~
1、概念介紹
表驅(qū)動法 是一種編程模式,從表里查找信息而不使用邏輯語句(if/else)
事實上,凡是能通過邏輯語句來選擇的事物,都可以通過查表來選擇。
對簡單的情況而言,使用邏輯語句更為容易和直白,但隨著邏輯鏈的越來越復雜,查表法也就愈發(fā)顯得更具有吸引力。
應用原則
適當?shù)那闆r下,采用表驅(qū)動法,所生成的代碼會比復雜的邏輯代碼更簡單,更容易修改,而且效率更高。
2、應用實踐
2.1 直接訪問
2.1.1 今天周幾?
傳統(tǒng)寫法:
- String today = "周日";
- Switch( dayForMonth % 7 ){
- case 0 :
- today = "周日";
- case 1 :
- today = "周一";
- case 2 :
- today = "周二";
- case 3 :
- today = "周三";
- case 4 :
- today = "周四";
- case 5 :
- today = "周五";
- default:
- today = "周六";
- }
表驅(qū)動法:
- String [] weekday = new String[]{"周日","周一","周二","周三","周四","周五","周六"};
- String today = weekday [ dayForMonth % 7 ];
2.1.2 每個月多少天?
傳統(tǒng)寫法:
- if(1 == iMonth) {
- iDays = 31;
- } else if(2 == iMonth) {
- iDays = 28;
- } else if(3 == iMonth) {
- iDays = 31;
- } else if(4 == iMonth) {
- iDays = 30;
- } else if(5 == iMonth) {
- iDays = 31;
- } else if(6 == iMonth) {
- iDays = 30;
- } else if(7 == iMonth) {
- iDays = 31;
- } else if(8 == iMonth) {
- iDays = 31;
- } else if(9 == iMonth) {
- iDays = 30;
- } else if(10 == iMonth) {
- iDays = 31;
- } else if(11 == iMonth) {
- iDays = 30;
- } else if(12 == iMonth) {
- iDays = 31;
- }
表驅(qū)動法:
把邏輯寫成 map 或是 list,一目了然,可以搞個2維數(shù)組還加上了閏年的邏輯。
- const monthDays = [
- [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
- [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
- ]
- function getMonthDays(month, year) {
- let isLeapYear = (year % 4 === 0) && (year % 100 !== 0 || year % 400 === 0) ? 1 : 0
- return monthDays[isLeapYear][(month - 1)];
- }
- console.log(getMonthDays(2, 2000))
2.2 索引訪問
有時只用一個簡單的數(shù)學運算還無法把 age 這樣的數(shù)據(jù)轉(zhuǎn)換成為表鍵值,這種情況可以通過索引訪問的方法加以解決。
索引應用:先用一個基本類型的數(shù)據(jù)從一張索引表中查出一個鍵值,然后在用這一鍵值查出需要的主數(shù)據(jù)。
舉例:
有100件商品,商店物品編號(范圍 0000-9999)
創(chuàng)建兩張表:索引表(0-9999),物品(查詢)表(0-100)
索引訪問有兩個優(yōu)點:
- 如果主查詢表的每條記錄都很大,那創(chuàng)建一個浪費了很多空間的數(shù)組所用的空間,要比建立主查詢表所用的空間小得多。
- 操作索引中的記錄比操作主查詢表的的記錄更方便,編寫到表里面的數(shù)據(jù)比嵌入代碼的數(shù)據(jù)更容易維護。
2.3 階梯訪問
這種訪問方法不像索引結(jié)構(gòu)那樣直接,但是它要比索引訪問方法節(jié)省空間。
階梯結(jié)構(gòu)的基本思想:表中的記錄對于不同數(shù)據(jù)范圍有效,而不是對不同的數(shù)據(jù)點有效。
舉例:
一個等級評定的應用程序,其中“B”記錄所對應的范圍是 75.0%-90.0%
>= 90.0% A
<90.0% B
<75.0% C
<65.0% D
<50.0% F
這種劃分范圍用在查詢表中是不合適的,因為你不能用簡單的數(shù)據(jù)轉(zhuǎn)換函數(shù)來把表鍵值轉(zhuǎn)換成 A-F 字母所代表的等級。用索引也不合適,因為這里用的是浮點數(shù)。
在應用階梯方法的時候,必須謹慎的處理范圍的端點。
- Dim rangeLimit() As Double = {50.0, 65.0, 75.0, 90.0, 100.0}
- Dim grade() As String={"F", "D", "C", "B", "A"}
- maxGradeLevel = grade.Length - 1
- // assign a grad to a student based on the student's score
- gradeLevel= 0
- studentGrade = ”A"
- while( studentGrade = "A" and gradeLevel < maxGradeLevel )
- if( studentScore < rangeLimit( gradeLevel ) ) then
- studentGrade = grade ( gradeLevel)
- end if
- gradeLevel = gradeLevel + 1
- wend
與其他表驅(qū)動法相比,這種方法的優(yōu)點在于它很適合處理那些無規(guī)則的數(shù)據(jù)。
在使用階梯訪問時需要注意的一些細節(jié):
1)留心邊界端點
注意邊界:< 與 <=,確認循環(huán)能夠在找出最高一級區(qū)間后恰當?shù)亟K止。
2)考慮用二分查找取代順序查找
如果列表很大,可以把它替換成一個準二分查找法,從頭查找是很耗費性能的
3)考慮用索引訪問來取代階梯訪問
階梯訪問中的查找操作可能會比較耗時,如果執(zhí)行速度很重要,那可以考慮用索引訪問來取代階梯查找,即以犧牲存儲空間來換取速度。
2.4 構(gòu)造查詢鍵值
如上述例子,我們希望能夠?qū)?shù)據(jù)作為鍵值直接訪問表,這樣既簡單又快速。
但是問題或者數(shù)據(jù)通常并不是這樣友好,那就需要引出 構(gòu)造查詢鍵值 的方法。
費率與年齡、性別、婚姻及交費年數(shù)等不同情況而變動。
1)復制信息從而能夠直接使用鍵值
age補齊:50 歲以上的年齡都復制一份 50 歲的費率。
這樣優(yōu)點在于表自身結(jié)構(gòu)非常簡單那,訪問表的邏輯也很簡單;
缺點在于復制生成的冗余信息會浪費空間,也即是利用空間換效率。
2)轉(zhuǎn)換鍵值以使其能夠直接使用
費率表查詢時,用一個函數(shù)將 age 轉(zhuǎn)換為另一個數(shù)值。
在此例子中,該函數(shù)必須把所有介于 0-5 直接的年齡轉(zhuǎn)換成一個鍵值,例如 5,同時把所有超過 50 的年齡都轉(zhuǎn)換成另一個鍵值,例如 50。
這樣在檢索前可以用 min()和 max()函數(shù)來做這一轉(zhuǎn)換。
例如,你可以用下述表達式:max(min(50, age), 17) 來生成一個介于 17-50 之間的表鍵值。
3)把鍵值轉(zhuǎn)換提取城獨立子程序
如果你必須要構(gòu)造一些數(shù)據(jù)來讓它們像表鍵值一樣使用,那就把數(shù)據(jù)到鍵值的轉(zhuǎn)換操作提取成獨立的子程序。這樣可避免在不同位置執(zhí)行了不同的轉(zhuǎn)換,也使得轉(zhuǎn)換操作修改起來更加容易。
任務是個方法,不再是數(shù)值了,這里我們可以利用 Dart 這樣的支持高階函數(shù)的語言特性,把方法當做一個對象存儲在表中。
- var data = <String, Map>{
- "A": {
- "name": "AA",
- "action": (name) => print(name + "/AA"),
- },
- "B": {
- "name": "BB",
- "action": (name) => print(name + "/BB"),
- },
- };
- var action = data["A"]["action"];
- action("kk");
3、總結(jié)
1)如何從表中查數(shù)據(jù)?
- List item
- 直接訪問
- 索引訪問
- 階梯訪問
2)在表里存些什么?
- 數(shù)據(jù)
- 動作(action)-描述該動作的代碼/該動作的子程序的引用。
表驅(qū)動法提供了一種復雜的邏輯和繼承結(jié)構(gòu)的替換方案。如果你發(fā)現(xiàn)自己對某個應用程序的邏輯或者繼承關(guān)系感到困惑,那是否可以通過一個查詢表來加以簡化。
- 使用表的關(guān)鍵決策是決定如何去訪問表,可以采取直接訪問、索引訪問或階梯訪問
- 使用表的另一項關(guān)鍵決策是決定如何去把什么內(nèi)容放入表中
- 需要保存浮點數(shù)和范圍數(shù)時,使用階梯訪問的形式。