成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

干掉項目中雜亂的 if-else,試試狀態模式,這才是優雅的實現方式!

開發 后端
原來以為寫一個簡單的類型翻譯器花不了太多時間,可是真做起來,才發現要注意的點太多了。

 IF-ELSE 方式

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

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

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

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

最終修修補補好多次,終于把代碼寫完了,連優化的想法都沒了,擔心又引入新的問題。更多 Java 核心技術教程:https://github.com/javastacks/javastack,一起來學習吧。

最終的偽代碼如下: 

  1. public String parseToFullType() throws IllegalStateException {  
  2.     StringBuilder sb = new StringBuilder();  
  3.     for (; ; this.scanner.next()) {  
  4.         Character currentChar = scanner.current();  
  5.         if (currentChar == '\uFFFF') {  
  6.             return sb.toString();  
  7.         }  
  8.         if (isCollection()) {  
  9.             if (CollectionEnd()) {  
  10.                 dealCollectionEleEnd();  
  11.             }else {  
  12.                 throw new IllegalStateException("unexpected char '" + currentChar + "' at position " + scanner.getIndex());  
  13.             }  
  14.         } else if (isWrapperType()) {  
  15.             dealSingleEleEnd();  
  16.         } else if (parseStart()) {  
  17.             if (collectionStart()) {  
  18.                 putCollecitonExpectEle()  
  19.             }  
  20.         } else {  
  21.             throw new IllegalStateException("unknown char '" + currentChar + "' at position " + scanner.getIndex());  
  22.         }  
  23.     }    

狀態機方式

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

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

狀態機

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

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

  •  狀態 State:狀態是一個系統在其生命周期的某一刻時的運行狀態,如駕車的例子中狀態就包括 正常速度行駛、停車和低速行駛三種狀態。
  •  事件 Event:事件就是某一時刻施加于系統的某個信號,在上面的例子中事件是指紅燈、綠燈和黃燈。所有的狀態變化都要依賴事件,但事件也可能導致狀態不發生變化,如正常行駛中遇到綠燈就不用做什么反應。
  •  變換 Transition:變換是在事件發生之后系統要做出的狀態變化,如上面例子中的減速、停車或加速。
  •  動作 Action:動作是同樣是事件發生之后系統做出的反應,不同的是,動作不會改變系統狀態,像駕車遇到紅燈停車后,喝水這個動作沒有對系統狀態造成影響。

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

狀態拆分

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

通過上面的圖一步步拆解狀態機:

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

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

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

  1. public interface StateHandler {  
  2.     /**  
  3.      * @param event 要處理的事件  
  4.      * @param states 系統整體狀態  
  5.      * @param result 解析的結果  
  6.      */  
  7.     void handle(Event event, Stack<State> states, StringBuilder result);  

代碼示例

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

  1. public class MapLeftHandler implements StateHandler {  
  2.     @Override  
  3.     public void handle(Event event, Stack<State> states, StringBuilder result) {  
  4.         // 這里是核心的 Action,將單步解析結果放到最終結果內  
  5.         result.append(",");  
  6.         result.append(event.getParsedVal());  
  7.         // 狀態機的典型處理方式,處理各種事件發生在當前狀態時的邏輯  
  8.         switch (event.getEventType()) {  
  9.             case MAP:  
  10.                 states.push(State.MAP_START);  
  11.                 break;  
  12.             case SET:  
  13.                 states.push(State.SET_START);  
  14.                 break;  
  15.             case LIST:  
  16.                 states.push(State.LIST_START);  
  17.                 break;  
  18.             case WRAPPED_ELE:  
  19.                 // 使用 pop 或 push 修改棧頂狀態來修改解析器的整體狀態  
  20.                 states.pop();  
  21.                 states.push(State.MAP_RIGHT);  
  22.                 break;  
  23.             case PRIMITIVE_ELE:  
  24.                 // 當前狀態不能接受的事件類型要拋異常中斷  
  25.                 throw new IllegalStateException("unexpected primitive char '" + event.getCharacter() + "' at position " + event.getIndex());  
  26.             default:  
  27.         }  
  28.     }  

主類內的代碼如下: 

  1. public static String parseToFullType(String shortenType) throws IllegalStateException {  
  2.     StringBuilder result = new StringBuilder();  
  3.     StringCharacterIterator scanner = new StringCharacterIterator(shortenType);  
  4.     Stack<State> states = new Stack<>();  
  5.     states.push(State.START);  
  6.     for (; ; scanner.next()) { 
  7.         char currentChar = scanner.current();  
  8.         if (currentChar == '\uFFFF') {  
  9.             return result.toString();  
  10.         }  
  11.         // 使用整體狀態為空來代表解析結束  
  12.         if (states.isEmpty()) {  
  13.             throw new IllegalStateException("unexpected char '" + currentChar + "' at position " + scanner.getIndex());  
  14.         }  
  15.         // 將字符規整成事件對象,有利于參數的傳遞  
  16.         Event event = Event.parseToEvent(currentChar, scanner.getIndex());  
  17.         if (event == null) {  
  18.             throw new IllegalStateException("unknown char '" + currentChar + "' at position " + scanner.getIndex());  
  19.         }  
  20.         // 這里需要一個 Map 來映射狀態和狀態處理器  
  21.         STATE_TO_HANDLER_MAPPING.get(states.peek()).handle(event, states, result);  
  22.     }  
  23.  

小結

狀態模式

如果你對設計模式較熟的話,會發現這不就是狀態模式嘛。

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

代碼結構

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

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

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

 

責任編輯:龐桂玉 來源: Hollis
相關推薦

2024-11-04 09:41:47

2020-04-09 08:29:50

編程語言事件驅動

2020-10-22 09:20:22

SQLNoSQL 數據庫

2021-01-29 07:45:27

if-else代碼數據

2022-07-04 08:32:55

Map函數式接口

2025-04-21 00:00:05

2020-07-17 13:01:44

If-Else代碼編程

2022-07-11 08:16:55

策略模式if-else

2022-06-14 10:49:33

代碼優化Java

2019-03-17 16:48:51

物聯網云計算數據信息

2013-03-06 10:28:57

ifJava

2023-06-02 07:30:24

If-else結構流程控制

2013-11-28 14:34:30

微軟WP

2021-04-13 06:39:13

代碼重構code

2021-11-10 16:03:42

Pyecharts Python可視化

2021-03-10 07:20:43

if-else靜態代碼

2020-12-29 09:16:36

程序員對象開發

2021-12-15 07:24:56

SocketTCPUDP

2024-09-25 08:22:06

2021-06-21 09:36:44

微信語音轉發
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩三级一区 | 亚洲精品播放 | 日本高清精品 | 91久久精品一区二区二区 | 欧美精品福利 | 国产不卡在线播放 | 国产欧美一区二区三区久久 | 精品久久一区二区三区 | 亚洲欧美日韩电影 | h视频免费在线观看 | 女同久久另类99精品国产 | 福利一区在线观看 | 久久激情网 | 久久精品国产亚洲 | 亚洲免费精品 | www.日本国产 | 亚洲成人一区 | 国产精品久久久久久久久久久久久久 | 国产一区久久 | av男人的天堂av | 精品一区二区视频 | 亚洲国产免费 | 色888www视频在线观看 | 日韩高清国产一区在线 | 国产成人精品av | 精品视频网 | 精品亚洲一区二区 | 久久精品一区 | 国产成人精品一区二区 | 黄色一级大片视频 | 亚洲视频手机在线 | 国产97色 | 亚洲a人| 超碰国产在线 | 日本精品一区二区在线观看 | 欧美大片黄 | 亚洲一区二区久久 | 国产综合在线视频 | 巨大荫蒂视频欧美另类大 | 特黄毛片视频 | 国产一区中文字幕 |