干掉if-else,試試狀態(tài)模式!
互聯(lián)網(wǎng)架構(gòu)師后臺(tái)回復(fù) 2T 有特別禮包
背景
玩轉(zhuǎn) Java 動(dòng)態(tài)編譯,實(shí)現(xiàn)了 Java 代碼的動(dòng)態(tài)編譯后,接下來(lái)就要將原來(lái)使用注釋配置的 Java 數(shù)據(jù)類型改為使用縮寫(xiě)替代。
為了便于縮寫(xiě),能直觀地看出完整類型,我設(shè)計(jì)的方案是:
對(duì)簡(jiǎn)單類型如 String、int、Double,就使用類型的首字母替代,如 i -> int / D -> Double; 對(duì)于容器類型如 List、Map,使用兩個(gè)首字母分別標(biāo)志容器的開(kāi)始和閉合,如 LDL -> List<Double> / MDDM -> Map<Double,Double>; 由于 Set 的首字母和 String 首字母沖突,將 String 的縮寫(xiě)修改為 T,同時(shí)處理了 Long 和 List 的沖突; 支持容器類型的嵌套,如 LLTLL -> List<List<String>> / MTLDLM -> Map<String,List<Double>>;
我使用普通的 if-else 方式和狀態(tài)機(jī)方式各實(shí)現(xiàn)了一遍,更深切地理解了狀態(tài)機(jī)在處理這種多狀態(tài)的復(fù)雜問(wèn)題時(shí)的優(yōu)越性。
原來(lái)以為寫(xiě)一個(gè)簡(jiǎn)單的類型翻譯器花不了太多時(shí)間,可是真做起來(lái),才發(fā)現(xiàn)要注意的點(diǎn)太多了。
首先是處理容器的開(kāi)啟和閉合,這就需要使用棧來(lái)保存預(yù)期的下一個(gè)字符類型,再對(duì)比棧頂字符類型和當(dāng)前處理字符,決定解析的結(jié)果。
還要注意類型嵌套的情況下,內(nèi)層嵌套的容器作為外層容器的元素被解析完成時(shí),需要修改外層容器的預(yù)期字符。而且 Map 作為一種相對(duì) Set 和 List 比較特殊的容器,還要處理它的左右元素。
同時(shí)還不能忘記處理各種異常,如未知字符、容器內(nèi)是原始類型、容器未正確閉合等。
最終修修補(bǔ)補(bǔ)好多次,終于把代碼寫(xiě)完了,連優(yōu)化的想法都沒(méi)了,擔(dān)心又引入新的問(wèn)題。
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)機(jī)
狀態(tài)拆分

首先是確定狀態(tài),我定義了 Start/SetStart/SetEle/ListStart/ListEel/MapStart/MapLeft/MapRight 八種基礎(chǔ)狀態(tài),由于一次只解析一個(gè)類型,容器閉合就代表著解析結(jié)束,所以沒(méi)有對(duì)各個(gè)容器設(shè)置結(jié)束狀態(tài)。又因?yàn)橛袪顟B(tài)嵌套的存在,而一個(gè)狀態(tài)沒(méi)法表達(dá)狀態(tài)機(jī)的準(zhǔn)確狀態(tài),需要使用棧來(lái)存儲(chǔ)整體的解析狀態(tài),我使用這個(gè)棧為空來(lái)代表 End 狀態(tài),又省略了一個(gè)狀態(tài)。另外,搜索公眾號(hào)互聯(lián)網(wǎng)架構(gòu)師回復(fù)關(guān)鍵字"2T”獲取一份驚喜禮包。
再拆分事件,事件是掃描到的每一個(gè)字符,由于字符種類較多,而像 integer 和 double、String 和 Long 的處理又沒(méi)有什么區(qū)別,我將事件類型抽象為 包裝類型元素(WRAPPED_ELE),原始類型元素(PRIMITIVE_ELE),MAP、List 和 Set 五種。 變幻和動(dòng)作都是事件發(fā)生后系統(tǒng)的反應(yīng),在我的需要里需要轉(zhuǎn)變解析狀態(tài),并將結(jié)構(gòu)結(jié)果保存起來(lái)。這里我將它們整體抽象為一個(gè)事件處理器接口,如:
public interface StateHandler {/*** @param event 要處理的事件* @param states 系統(tǒng)整體狀態(tài)* @param result 解析的結(jié)果*/void handle(Event event, Stack<State> states, StringBuilder result);}
將狀態(tài)機(jī)的各個(gè)要素都抽出來(lái)之后,再分別完善每個(gè) StateHandler 的處理邏輯就行,這部分就非常簡(jiǎn)單了,下面是 MapLeftHandler 的詳情。
public class MapLeftHandler implements StateHandler {@Overridepublic void handle(Event event, Stack<State> states, StringBuilder result) {// 這里是核心的 Action,將單步解析結(jié)果放到最終結(jié)果內(nèi)result.append(",");result.append(event.getParsedVal());// 狀態(tài)機(jī)的典型處理方式,處理各種事件發(fā)生在當(dāng)前狀態(tài)時(shí)的邏輯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)來(lá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:}}}
public static String parseToFullType(String shortenType) throws IllegalStateException {StringBuilder result = new StringBuilder();StringCharacterIterator scanner = new StringCharacterIterator(shortenType);Stack<State> states = new Stack<>();states.push(State.START);for (; ; scanner.next()) {char currentChar = scanner.current();if (currentChar == '\uFFFF') {return result.toString();}// 使用整體狀態(tài)為空來(lái)代表解析結(jié)束if (states.isEmpty()) {throw new IllegalStateException("unexpected char '" + currentChar + "' at position " + scanner.getIndex());}// 將字符規(guī)整成事件對(duì)象,有利于參數(shù)的傳遞Event event = Event.parseToEvent(currentChar, scanner.getIndex());if (event == null) {throw new IllegalStateException("unknown char '" + currentChar + "' at position " + scanner.getIndex());}// 這里需要一個(gè) Map 來(lái)映射狀態(tài)和狀態(tài)處理器STATE_TO_HANDLER_MAPPING.get(states.peek()).handle(event, states, result);}}
有解釋說(shuō),狀態(tài)模式會(huì)將事件類型也再解耦,即 StateHandler 里不只有一個(gè)方法,而是會(huì)有八個(gè)方法,分別為 handleStart,HandleListEle 等,但我覺(jué)得模式并不是定式,稍微的變形是沒(méi)有問(wèn)題的,如果單個(gè)事件類型的處理足夠復(fù)雜,將其再拆分更合理一些。
代碼結(jié)構(gòu)
最后,關(guān)注公眾號(hào)互聯(lián)網(wǎng)架構(gòu)師,在后臺(tái)回復(fù):2T,可以獲取我整理的 Java 系列面試題和答案,非常齊全。
正文結(jié)束
1.心態(tài)崩了!稅前2萬(wàn)4,到手1萬(wàn)4,年終獎(jiǎng)扣稅方式1月1日起施行~
2.深圳一普通中學(xué)老師工資單曝光,秒殺程序員,網(wǎng)友:敢問(wèn)是哪個(gè)學(xué)校畢業(yè)的?
3.從零開(kāi)始搭建創(chuàng)業(yè)公司后臺(tái)技術(shù)棧
5.清華大學(xué):2021 元宇宙研究報(bào)告!
6.為什么國(guó)內(nèi) 996 干不過(guò)國(guó)外的 955呢?

