<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          干掉項目中雜亂的 if-else,試試狀態(tài)模式,這才是優(yōu)雅的實現(xiàn)方式!

          共 6090字,需瀏覽 13分鐘

           ·

          2022-01-19 10:46

          來源:https://zhenbianshu.github.io

          接上文玩轉(zhuǎn) Java 動態(tài)編譯,實現(xiàn)了 Java 代碼的動態(tài)編譯后,接下來就要填補上文中提到的坑,將原來使用注釋配置的 Java 數(shù)據(jù)類型改為使用縮寫替代。

          為了便于縮寫,能直觀地看出完整類型,我設(shè)計的方案是:

          1. 對簡單類型如 String、int、Double,就使用類型的首字母替代,如 i -> int / D -> Double;
          2. 對于容器類型如 List、Map,使用兩個首字母分別標志容器的開始和閉合,如 LDL -> List / MDDM -> Map
          3. 由于 Set 的首字母和 String 首字母沖突,將 String 的縮寫修改為 T,同時處理了 Long 和 List 的沖突;
          4. 支持容器類型的嵌套,如 LLTLL -> List> / MTLDLM -> Map>;

          我使用普通的 if-else 方式和狀態(tài)機方式各實現(xiàn)了一遍,更深切地理解了狀態(tài)機在處理這種多狀態(tài)的復(fù)雜問題時的優(yōu)越性。

          兩種實現(xiàn)的代碼我都放在了 github 上,地址是:https://github.com/zhenbianshu/java-shorten-type-parser,有類似需求的可以改改來用。

          IF-ELSE 方式

          原來以為寫一個簡單的類型翻譯器花不了太多時間,可是真做起來,才發(fā)現(xiàn)要注意的點太多了。

          首先是處理容器的開啟和閉合,這就需要使用來保存預(yù)期的下一個字符類型,再對比棧頂字符類型和當(dāng)前處理字符,決定解析的結(jié)果。

          還要注意類型嵌套的情況下,內(nèi)層嵌套的容器作為外層容器的元素被解析完成時,需要修改外層容器的預(yù)期字符。而且 Map 作為一種相對 Set 和 List 比較特殊的容器,還要處理它的左右元素。同時還不能忘記處理各種異常,如未知字符、容器內(nèi)是原始類型、容器未正確閉合等。

          而這些邏輯混雜在一塊就更添復(fù)雜度了,通常是一遍代碼寫下來挺順暢,找?guī)讉€特殊的 case 一驗證,往往就有沒有考慮到的點,你以為解決了這個點就好了,殊不知這個問題點的解決方案又引起了另一個問題。

          最終修修補補好多次,終于把代碼寫完了,連優(yōu)化的想法都沒了,擔(dān)心又引入新的問題。更多 Java 核心技術(shù)教程:https://github.com/javastacks/javastack,一起來學(xué)習(xí)吧。

          最終的偽代碼如下:

          public?String?parseToFullType()?throws?IllegalStateException?{
          ????StringBuilder?sb?=?new?StringBuilder();

          ????for?(;?;?this.scanner.next())?{
          ????????Character?currentChar?=?scanner.current();
          ????????if?(currentChar?==?'\uFFFF')?{
          ????????????return?sb.toString();
          ????????}
          ????????if?(isCollection())?{
          ????????????if?(CollectionEnd())?{
          ????????????????dealCollectionEleEnd();
          ????????????}else?{
          ????????????????throw?new?IllegalStateException("unexpected?char?'"?+?currentChar?+?"'?at?position?"?+?scanner.getIndex());
          ????????????}
          ????????}?else?if?(isWrapperType())?{
          ????????????dealSingleEleEnd();
          ????????}?else?if?(parseStart())?{
          ????????????if?(collectionStart())?{
          ????????????????putCollecitonExpectEle()
          ????????????}
          ????????}?else?{
          ????????????throw?new?IllegalStateException("unknown?char?'"?+?currentChar?+?"'?at?position?"?+?scanner.getIndex());
          ????????}
          ????}

          狀態(tài)機方式

          是不是看起來非常亂,這還沒有列出各個方法里的條件判斷語句呢。這么多邏輯混雜,造成的問題就是很難改動,因為你不知道改動會影響哪些其他邏輯。

          面對這種問題,當(dāng)然有一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計經(jīng)驗的總結(jié),就是狀態(tài)機。

          狀態(tài)機

          有限狀態(tài)機(finite-state machine,縮寫:FSM)又稱有限狀態(tài)自動機(finite-state automation,縮寫:FSA),簡稱狀態(tài)機,是表示有限個狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動作等行為的數(shù)學(xué)計算模型。

          像我們生活中在公路上駕駛汽車就像在維護一個狀態(tài)機,遇到紅燈就停車喝口水,紅燈過后再繼續(xù)行車,遇到了黃燈就要減速慢行。而實現(xiàn)狀態(tài)機前要首先明確四個主體:

          將狀態(tài)機的四種要素提取之后,就可以很簡單地將狀態(tài)和事件進行解耦了。

          狀態(tài)拆分

          還是拿我的這個需求來分析,先畫出狀態(tài)變化圖從整體上把握狀態(tài)間的關(guān)系。

          通過上面的圖一步步拆解狀態(tài)機:

          1. 首先是確定狀態(tài),我定義了 Start/SetStart/SetEle/ListStart/ListEel/MapStart/MapLeft/MapRight 八種基礎(chǔ)狀態(tài),由于一次只解析一個類型,容器閉合就代表著解析結(jié)束,所以沒有對各個容器設(shè)置結(jié)束狀態(tài)。又因為有狀態(tài)嵌套的存在,而一個狀態(tài)沒法表達狀態(tài)機的準確狀態(tài),需要使用棧來存儲整體的解析狀態(tài),我使用這個棧為空來代表 End 狀態(tài),又省略了一個狀態(tài)。

          2. 再拆分事件,事件是掃描到的每一個字符,由于字符種類較多,而像 integer 和 double、String 和 Long 的處理又沒有什么區(qū)別,我將事件類型抽象為 包裝類型元素(WRAPPED_ELE),原始類型元素(PRIMITIVE_ELE),MAP、List 和 Set 五種。

          3. 變幻和動作都是事件發(fā)生后系統(tǒng)的反應(yīng),在我的需要里需要轉(zhuǎn)變解析狀態(tài),并將結(jié)構(gòu)結(jié)果保存起來。這里我將它們整體抽象為一個事件處理器接口,如:

          public?interface?StateHandler?{
          ????/**
          ?????*?@param?event?要處理的事件
          ?????*?@param?states?系統(tǒng)整體狀態(tài)
          ?????*?@param?result?解析的結(jié)果
          ?????*/
          ????void?handle(Event?event,?Stack?states,?StringBuilder?result);
          }

          代碼示例

          將狀態(tài)機的各個要素都抽出來之后,再分別完善每個 StateHandler 的處理邏輯就行,這部分就非常簡單了,下面是 MapLeftHandler 的詳情。

          public?class?MapLeftHandler?implements?StateHandler?{
          ????@Override
          ????public?void?handle(Event?event,?Stack?states,?StringBuilder?result)?{
          ????????//?這里是核心的?Action,將單步解析結(jié)果放到最終結(jié)果內(nèi)
          ????????result.append(",");
          ????????result.append(event.getParsedVal());

          ????????//?狀態(tài)機的典型處理方式,處理各種事件發(fā)生在當(dāng)前狀態(tài)時的邏輯
          ????????switch?(event.getEventType())?{
          ????????????case?MAP:
          ????????????????states.push(State.MAP_START);
          ????????????????break;
          ????????????case?SET:
          ????????????????states.push(State.SET_START);
          ????????????????break;
          ????????????case?LIST:
          ????????????????states.push(State.LIST_START);
          ????????????????break;
          ????????????case?WRAPPED_ELE:
          ????????????????//?使用?pop?或?push?修改棧頂狀態(tài)來修改解析器的整體狀態(tài)
          ????????????????states.pop();
          ????????????????states.push(State.MAP_RIGHT);
          ????????????????break;
          ????????????case?PRIMITIVE_ELE:
          ????????????????//?當(dāng)前狀態(tài)不能接受的事件類型要拋異常中斷
          ????????????????throw?new?IllegalStateException("unexpected?primitive?char?'"?+?event.getCharacter()?+?"'?at?position?"?+?event.getIndex());
          ????????????default:
          ????????}
          ????}
          }

          主類內(nèi)的代碼如下:

          public?static?String?parseToFullType(String?shortenType)?throws?IllegalStateException?{
          ????StringBuilder?result?=?new?StringBuilder();
          ????StringCharacterIterator?scanner?=?new?StringCharacterIterator(shortenType);
          ????Stack?states?=?new?Stack<>();
          ????states.push(State.START);

          ????for?(;?;?scanner.next())?{
          ????????char?currentChar?=?scanner.current();
          ????????if?(currentChar?==?'\uFFFF')?{
          ????????????return?result.toString();
          ????????}
          ????????//?使用整體狀態(tài)為空來代表解析結(jié)束
          ????????if?(states.isEmpty())?{
          ????????????throw?new?IllegalStateException("unexpected?char?'"?+?currentChar?+?"'?at?position?"?+?scanner.getIndex());
          ????????}
          ????????//?將字符規(guī)整成事件對象,有利于參數(shù)的傳遞
          ????????Event?event?=?Event.parseToEvent(currentChar,?scanner.getIndex());
          ????????if?(event?==?null)?{
          ????????????throw?new?IllegalStateException("unknown?char?'"?+?currentChar?+?"'?at?position?"?+?scanner.getIndex());
          ????????}

          ????????//?這里需要一個?Map?來映射狀態(tài)和狀態(tài)處理器
          ????????STATE_TO_HANDLER_MAPPING.get(states.peek()).handle(event,?states,?result);
          ????}
          }

          小結(jié)

          狀態(tài)模式

          如果你對設(shè)計模式較熟的話,會發(fā)現(xiàn)這不就是狀態(tài)模式嘛。更多設(shè)計模式系列整理好了,微信搜索Java技術(shù)棧,在后臺發(fā)送:設(shè)計模式,可以獲取閱讀。

          有解釋說,狀態(tài)模式會將事件類型也再解耦,即 StateHandler 里不只有一個方法,而是會有八個方法,分別為 handleStart,HandleListEle 等,但我覺得模式并不是定式,稍微的變形是沒有問題的,如果單個事件類型的處理足夠復(fù)雜,將其再拆分更合理一些。

          代碼結(jié)構(gòu)

          最后,對比 if-else 實現(xiàn),從代碼量上來看,狀態(tài)機實現(xiàn)增加了很多,這是解耦的代價,當(dāng)然也有很多重復(fù)代碼的緣故,比如在容器閉合時校驗當(dāng)前容器是否內(nèi)嵌容器,并針對內(nèi)嵌容器做處理的邏輯就完全一樣,為了代碼清晰我就沒有再抽取方法。

          從可維護性上來說,狀態(tài)機實現(xiàn)由于邏輯拆分比較清晰,在添加或刪除一種狀態(tài)時比較方便,添加一個狀態(tài)和狀態(tài)處理器就行,但在添加一種事件類型時較為復(fù)雜,需要修改所有狀態(tài)處理器里的實現(xiàn),不過從整體上來看是利大于弊的,畢竟代碼清晰易改動最重要。

          了解了狀態(tài)機實現(xiàn)的固定套路之后,你也可以寫出高大上的狀態(tài)機代碼了,快 Get 起來替換掉項目里雜亂的 if-else 吧。

          2022年,我們一起加油(班)!


          往期推薦


          空降的大廠 CTO,半年內(nèi)成功把團隊帶垮了!
          程序員床上二三事 …
          “阿里味” PUA 編程語言,火上GitHub熱榜,標星2.7K!



          長按進入小程序,進行打卡簽到


          小程序送書福利更新啦!

          (更多精彩值得期待……)


          2T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,人工智能,考研,軟考,英語,等等。在公眾號內(nèi)回復(fù)「資源」,即可免費獲?。?span style="outline: 0px;letter-spacing: 0.544px;font-weight: bolder;">回復(fù)「社群」,可以邀請你加入讀者群!


          “在看”點一點

          知足常樂??

          ???????

          瀏覽 76
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  www.天天干 | 久久久久久这里只有好吊视频 | 能看的黄色视频 | 一级大片网站 | 成人毛片18女人毛片软件下载 |