切換“理解 Parse 與狀態(tài)驅(qū)動(dòng)模式)
從圖形狀態(tài)切換理解 Parse 與狀態(tài)驅(qū)動(dòng)模式面向初學(xué)者 | 無(wú)代碼純思路目錄先看一個(gè)場(chǎng)景什么是 Parse為什么圖形能自動(dòng)變色一個(gè)數(shù)字裝下很多個(gè)狀態(tài)為什么不用一個(gè)狀態(tài)屬性而是散落的 9 個(gè)三個(gè)轉(zhuǎn)換器各管一攤從頭到尾走一遍總結(jié)先看一個(gè)場(chǎng)景想象你在操作一個(gè)檢測(cè)軟件畫布上有很多矩形圖形。當(dāng)發(fā)生以下情況時(shí)圖形外觀會(huì)變化鼠標(biāo)點(diǎn)擊圖形 → 邊框變虛線檢測(cè)結(jié)果 OK → 背景變綠色檢測(cè)結(jié)果 NG → 背景變紅色正在掃描 → 背景變橙色既沒(méi)選中也沒(méi)狀態(tài) → 默認(rèn)實(shí)線邊框問(wèn)題來(lái)了程序怎么知道一個(gè)圖形當(dāng)前是什么狀態(tài)然后自動(dòng)切換顏色什么是 ParseParse 讀懂一份數(shù)據(jù)翻譯成另一種格式。生活類比原始數(shù)據(jù)翻譯后看體溫計(jì)水銀柱“38.5 度”看紅綠燈亮紅燈“停”讀成績(jī)單“張三 數(shù)學(xué) 85”{名字:“張三”, 科目:“數(shù)學(xué)”, 成績(jī):85}程序里的 Parse 完全一樣輸入一堆碎片信息 → 輸出一個(gè)有意義的整體為什么圖形能自動(dòng)變色第一步ViewModel 告訴外界我有什么屬性每個(gè)圖形對(duì)象對(duì)外暴露一組屬性全是是/否這個(gè)圖形被選中了嗎 → 是 / 否 這個(gè)圖形被鼠標(biāo)懸停了嗎 → 是 / 否 這個(gè)圖形正在被掃描嗎 → 是 / 否 這個(gè)圖形掃描完了嗎 → 是 / 否 這個(gè)圖形正在重建嗎 → 是 / 否 這個(gè)圖形重建完了嗎 → 是 / 否 這個(gè)圖形正在檢測(cè)嗎 → 是 / 否 這個(gè)圖形檢測(cè)結(jié)果 OK → 是 / 否 這個(gè)圖形檢測(cè)結(jié)果 NG → 是 / 否總共 9 個(gè)問(wèn)題每個(gè)答案不是是就是否。第二步WPF 的多路綁定把這些答案打包送出WPF 有一個(gè)機(jī)制叫MultiBinding多路綁定它能把多個(gè)屬性同時(shí)、按順序送給一個(gè)處理函數(shù)MultiBinding 說(shuō)我把 9 個(gè)屬性的值打包成一個(gè)數(shù)組你按順序拿—— 第一個(gè)位置 → 是否選中 第二個(gè)位置 → 是否懸停 第三個(gè)位置 → 是否掃描 ... 第九個(gè)位置 → 是否 NG第三步Parse 函數(shù)把是/否轉(zhuǎn)換成狀態(tài)對(duì)象收到的就是一堆 true / false比如[true, false, true, false, false, false, false, false, false]Parse 的工作讀懂這些 true/false 分別表示什么把它們合并成一個(gè)復(fù)合狀態(tài)。[true, false, true, ...] ↓ 這個(gè)圖形被選中了 正在掃描中第四步轉(zhuǎn)換器根據(jù)狀態(tài)決定顏色Parse 輸出的狀態(tài)交給不同的轉(zhuǎn)換器Parse 輸出的狀態(tài) │ ┌────────────────┼────────────────┐ 邊框轉(zhuǎn)換器 背景轉(zhuǎn)換器 描邊轉(zhuǎn)換器 決定線型 決定背景色 決定描邊色每個(gè)轉(zhuǎn)換器做的事情就是查表如果狀態(tài)里有NG → 給紅色 如果狀態(tài)里有正在檢測(cè) → 給青色 如果狀態(tài)里有選中 → 給虛線 如果什么都沒(méi)有 → 給實(shí)線默認(rèn)色有優(yōu)先級(jí)NG OK 檢測(cè)中 掃描中 懸停 選中 默認(rèn)。一個(gè)數(shù)字裝下很多個(gè)狀態(tài)你會(huì)問(wèn)一個(gè)圖形同時(shí)選中又正在掃描又結(jié)果 NG怎么存用位標(biāo)記技巧把每個(gè)狀態(tài)分配一個(gè)二進(jìn)制位選中 → 第 0 位 懸停 → 第 1 位 掃描中 → 第 2 位 檢測(cè)中 → 第 5 位 結(jié)果 OK → 第 6 位 結(jié)果 NG → 第 7 位 ...一個(gè) int 有 32 位足夠裝下所有狀態(tài)。一個(gè)圖形選中 掃描中 NG → 第 0 位置 1、第 2 位置 1、第 7 位置 1 → 最終存成一個(gè)整數(shù)查詢時(shí)很簡(jiǎn)單狀態(tài)里有沒(méi)有 NG→ 看看第 7 位是不是 1。特殊處理OK 和 NG 互斥一個(gè)檢測(cè)結(jié)果不可能同時(shí) OK 又 NG。所以加一條規(guī)則如果傳入OK → 先加上 OK 標(biāo)記再擦掉 NG 標(biāo)記 如果傳入NG → 先加上 NG 標(biāo)記再擦掉 OK 標(biāo)記后寫入的會(huì)覆蓋前一個(gè)。基礎(chǔ)語(yǔ)法課堂如果你是初學(xué)者下面這些寫法可能會(huì)讓你困惑。我們用人話解釋一下。位運(yùn)算|、|、、~這組符號(hào)用來(lái)操控位標(biāo)記狀態(tài)。加標(biāo)記|OR或我有一個(gè)狀態(tài) 選中第 0 位是 1 我想再加正在掃描 選中 | 正在掃描 結(jié)果 選中 正在掃描兩個(gè) 1|的作用是只要任意一邊的位是 1結(jié)果就是 1。所以適合往上加標(biāo)記。|是|的簡(jiǎn)寫狀態(tài) 狀態(tài) | 正在掃描 ← 完整寫法 狀態(tài) | 正在掃描 ← 簡(jiǎn)寫效果一樣就像x x 5可以簡(jiǎn)寫成x 5。查標(biāo)記AND與想知道狀態(tài)里有沒(méi)有NG 狀態(tài) NG → 如果結(jié)果不為 0說(shuō)明有 NG的作用是只有兩邊的位都是 1結(jié)果才是 1。狀態(tài) 選中 | NG 第 0 位和第 10 位是 1 狀態(tài) NG 非零 → 有 NG 狀態(tài) OK 0 → 沒(méi)有 OK擦除標(biāo)記~配合NOT取反想擦掉狀態(tài)里的OK標(biāo)記 狀態(tài) 狀態(tài) (~OK)~把 OK 的二進(jìn)制位全部翻轉(zhuǎn)原來(lái) OK 對(duì)應(yīng)位是 1翻轉(zhuǎn)后變成 0原來(lái)別的位是 0翻轉(zhuǎn)后全是 1。然后用去與一下OK 對(duì)應(yīng)的位會(huì)變成 0被擦掉其他位保持不變。OK 0001000000 假設(shè) OK 占第 6 位 ~OK 1110111111 除了第 6 位其他全是 1 狀態(tài) ~OK → OK 對(duì)應(yīng)的位被清 0其他位原樣保留三元表達(dá)式條件 ? 是 : 否這是if-else的迷你版if (條件) { 取值A(chǔ); } else { 取值B; } 等價(jià)于條件 ? 取值A(chǔ) : 取值B通常用來(lái)做防御防止傳入的值是 null 導(dǎo)致崩潰傳入值可能是 null → 如果是 null用 false 代替 傳入值可能是 abc → 不是 bool也用 false 代替 傳入值 true → 正常使用is bool 變量名模式匹配這個(gè)東西同時(shí)干了三件事某個(gè)值 is bool s第幾步干什么舉例①判斷這個(gè)值是不是 bool 類型null 不是 / “abc” 不是 / true 是②如果是把值存到變量 s 里s true③如果否s 不存在整個(gè)表達(dá)式為 false—變量名沒(méi)有含義叫 s、flag、x 都行用完就扔。極簡(jiǎn)寫法某個(gè)值 is true不需要起變量名直接判斷是不是 true。null 和 false 都會(huì)被視為不是 true。WPF MultiBinding 怎么傳參數(shù)WPF 會(huì)把 XAML 里綁定的屬性按順序打包成數(shù)組XAML 寫的順序 第 1 個(gè)綁定 → 數(shù)組[0] 第 2 個(gè)綁定 → 數(shù)組[1] 第 3 個(gè)綁定 → 數(shù)組[2]下標(biāo)就是綁定的序號(hào)。轉(zhuǎn)換器收到的就是一個(gè)object[]里面每個(gè)位置對(duì)應(yīng)一個(gè)綁定的屬性值。注意東西類型是object而不是bool。因?yàn)?WPF 的綁定系統(tǒng)在傳值時(shí)不檢查類型什么值都能塞進(jìn)去null、字符串、數(shù)字……都可能。所以轉(zhuǎn)換器里必須做類型判斷。一句話總結(jié)這堆語(yǔ)法符號(hào)作用一句話加標(biāo)記加標(biāo)記簡(jiǎn)寫查標(biāo)記“看看某一位是不是 1”~取反“除了這一位其他全變” ~擦標(biāo)記“把某一位清零”? :三元“迷你 if-else”is bool s模式匹配“判斷 取值 防御”數(shù)組[下標(biāo)]取值“第幾個(gè)綁定的屬性”為什么不用一個(gè)狀態(tài)屬性而是散落的 9 個(gè)理想的設(shè)計(jì)是ViewModel 直接暴露一個(gè)狀態(tài)屬性而不是 9 個(gè)散落的 true/false。// 理想 圖形.當(dāng)前狀態(tài) 選中 | 掃描中 | 結(jié)果OK // 實(shí)際情況 圖形.是否選中 true 圖形.是否掃描中 true 圖形.是否結(jié)果OK true ...為什么沒(méi)按理想來(lái)因?yàn)檫@些 true/false 屬性不只是為 UI 顏色服務(wù)的。它們還要被業(yè)務(wù)邏輯直接讀取如果選中了 → 啟用刪除按鈕 如果正在掃描 → 禁用編輯這些業(yè)務(wù)判斷直接讀是否選中比查位標(biāo)記要直觀。所以屬性先以散落的 bool 形式存在后來(lái)要給 UI 加自動(dòng)變色功能時(shí)用 MultiBinding 打了個(gè)補(bǔ)丁。這是真實(shí)項(xiàng)目中常見(jiàn)的歷史選擇——不是最優(yōu)方案但改動(dòng)成本高于收益。按長(zhǎng)度分組的潛在問(wèn)題Parse 函數(shù)里有一段按傳了多少個(gè)屬性做分組判斷的邏輯。這種寫法有一個(gè)隱患如果一個(gè)圖形只綁了是否選中這一個(gè)屬性長(zhǎng)度1那么是否選中的狀態(tài)會(huì)被靜默跳過(guò)永遠(yuǎn)解析不到。現(xiàn)在沒(méi)出 bug純粹因?yàn)樗杏玫竭@個(gè)轉(zhuǎn)換器的圖形都恰好綁了至少兩個(gè)屬性但并沒(méi)有任何機(jī)制保證這一點(diǎn)。三個(gè)轉(zhuǎn)換器各管一攤轉(zhuǎn)換器管什么怎么判斷邊框轉(zhuǎn)換器實(shí)線/虛線/無(wú)線只看選中和懸停背景轉(zhuǎn)換器填充色按 NG OK 檢測(cè)中 掃描 懸停 優(yōu)先級(jí)描邊轉(zhuǎn)換器邊框顏色同背景邏輯但用不透明顏色同一個(gè)狀態(tài)值三個(gè)轉(zhuǎn)換器分別讀自己關(guān)心的部分互不干擾。從頭到尾走一遍1. 用戶點(diǎn)擊一個(gè)圖形 → 圖形.是否選中 true 2. WPF 檢測(cè)到屬性變化 → 把 9 個(gè)屬性值打包[true, false, false, ...] 3. Parse 處理 → 讀第一個(gè)位置true → 打上選中標(biāo)記 → 讀其他位置都是 false → 不加標(biāo)記 → 輸出狀態(tài) 選中 4. 邊框轉(zhuǎn)換器拿到選中 → 查表選中 → 虛線 5. 背景轉(zhuǎn)換器拿到選中 → 按優(yōu)先級(jí)查表不是 NG不是 OK不是檢測(cè)...是選中 → 透明色 6. 描邊轉(zhuǎn)換器拿到選中 → 選中 → 返回默認(rèn)描邊色 7. 畫布刷新 → 圖形邊框變虛線用戶看到視覺(jué)反饋整個(gè)過(guò)程無(wú)需手寫任何如果 xx 就把顏色設(shè)為 yy的代碼全部由轉(zhuǎn)換器自動(dòng)處理。總結(jié)Parse 讀懂原始數(shù)據(jù)翻譯成結(jié)構(gòu)化信息。像體溫計(jì)把水銀柱翻譯成38.5 度。MultiBinding 把多個(gè)屬性同時(shí)打包送給一個(gè)處理函數(shù)。像快遞員一次性取走 9 個(gè)包裹。位標(biāo)記 一個(gè)整數(shù)裝下多個(gè)是/否。每個(gè)狀態(tài)占一個(gè)二進(jìn)制位加一起就是一個(gè)整數(shù)。轉(zhuǎn)換器 查表決策。輸入狀態(tài) → 按優(yōu)先級(jí)查表 → 輸出對(duì)應(yīng)的顏色/線型。散落的 9 個(gè) bool 是歷史選擇。不是最優(yōu)但改動(dòng)成本高于收益。整個(gè)模式的本質(zhì)散落的 bool 值數(shù)據(jù) ↓ Parse 結(jié)構(gòu)化狀態(tài)語(yǔ)義 ↓ Converter 視覺(jué)表現(xiàn)顏色/線型數(shù)據(jù) → 語(yǔ)義 → 表現(xiàn)三層分離各不越界。