1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 6375字,需瀏覽 13分鐘

         ·

        2022-02-15 21:19


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


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


        • 對簡單類型如 String、int、Double,就使用類型的首字母替代,如 i -> int / D -> Double;

        • 對于容器類型如 List、Map,使用兩個首字母分別標志容器的開始和閉合,如 LDL -> List / MDDM -> Map

        • 由于 Set 的首字母和 String 首字母沖突,將 String 的縮寫修改為 T,同時處理了 Long 和 List 的沖突;

        • 支持容器類型的嵌套,如 LLTLL -> List> / MTLDLM -> Map>;


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


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



        1

        IF-ELSE 方式


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


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


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


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


        最終修修補補好多次,終于把代碼寫完了,連優(yōu)化的想法都沒了,擔心又引入新的問題。


        最終的偽代碼如下:


        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());
        ????????}
        ????}
        }



        2

        狀態(tài)機方式


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


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


        狀態(tài)機


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


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


        • 狀態(tài) State:狀態(tài)是一個系統(tǒng)在其生命周期的某一刻時的運行狀態(tài),如駕車的例子中狀態(tài)就包括 正常速度行駛、停車和低速行駛三種狀態(tài)。

        • 事件 Event:事件就是某一時刻施加于系統(tǒng)的某個信號,在上面的例子中事件是指紅燈、綠燈和黃燈。所有的狀態(tài)變化都要依賴事件,但事件也可能導致狀態(tài)不發(fā)生變化,如正常行駛中遇到綠燈就不用做什么反應。

        • 變換 Transition:變換是在事件發(fā)生之后系統(tǒng)要做出的狀態(tài)變化,如上面例子中的減速、停車或加速。

        • 動作 Action:動作是同樣是事件發(fā)生之后系統(tǒng)做出的反應,不同的是,動作不會改變系統(tǒng)狀態(tài),像駕車遇到紅燈停車后,喝水這個動作沒有對系統(tǒng)狀態(tài)造成影響。


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


        狀態(tài)拆分



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


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


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

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

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


        public?interface?StateHandler?{
        ????/**
        ?????* @param?event 要處理的事件
        ?????* @param?states 系統(tǒng)整體狀態(tài)
        ?????* @param?result 解析的結果
        ?????*/

        ????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,將單步解析結果放到最終結果內
        ????????result.append(",");
        ????????result.append(event.getParsedVal());

        ????????// 狀態(tài)機的典型處理方式,處理各種事件發(fā)生在當前狀態(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:
        ????????????????// 當前狀態(tài)不能接受的事件類型要拋異常中斷
        ????????????????throw?new?IllegalStateException("unexpected primitive char '"?+ event.getCharacter() + "' at position "?+ event.getIndex());
        ????????????default:
        ????????}
        ????}
        }


        主類內的代碼如下:


        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)為空來代表解析結束
        ????????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);
        ????}
        }



        3

        小結


        狀態(tài)模式


        如果你對設計模式較熟的話,會發(fā)現(xiàn)這不就是狀態(tài)模式嘛。


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


        代碼結構


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


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


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


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



        往期推薦



        SpringBoot+flowable快速實現(xiàn)工作流,優(yōu)秀的工作流輪子

        節(jié)后面試必備:Spring 面試常見問題,做好跳槽準備了嘛!?

        SpringBoot 那些自帶 Buff 的工具類,你用過幾個?

        幾款實用的內網穿透工具,推薦!

        神器 Markmap ?。?!

        MySQL 的 varchar 水真的太深了??!



        瀏覽 31
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            A級處女黃片免費看 | 免费色色网站 | 日本在线一二三区 | 五月天激情国产综合 | 中国少妇的呻吟xvideshd | 人妻在线导航 | 色噜噜亚洲欧美在线视频 | 午夜黄色福利视频 | 大尺度电影免费观看完整版 | 少妇口述与子做过爱 |