狀態(tài)機(jī)與狀態(tài)模式

又是很長(zhǎng)時(shí)間沒有寫博客了(一個(gè)月)...最近在做一個(gè)SpringBoot+Vue的項(xiàng)目御板,所以一直在看spring相關(guān)的東西。今天要學(xué)習(xí)的跟spring
沒有關(guān)系蚀腿,是我在之前維護(hù)的一個(gè)測(cè)試工具是遇到的一個(gè)知識(shí)點(diǎn)--狀態(tài)機(jī)

這個(gè)測(cè)試的一個(gè)功能就是解析自己定義的一套腳本語(yǔ)法規(guī)則,涉及到對(duì)輸入的語(yǔ)句進(jìn)行解析,然后下發(fā)到對(duì)應(yīng)的執(zhí)行器去執(zhí)行亦镶。

之前的解析邏輯是用一個(gè)while循環(huán),對(duì)每一個(gè)字符判斷袱瓮,然后各種if...else和臨時(shí)變量...總之讀起來十分費(fèi)勁缤骨,并且總?cè)菀壮鯞UG,而且十分不容易修改尺借,因?yàn)槊恳粋€(gè)修改都很容易影響到原來的解析結(jié)果绊起。
于是我把這段解析的代碼重構(gòu)了一遍,就是使用了狀態(tài)機(jī)的思想燎斩。

什么是狀態(tài)機(jī)

有限狀態(tài)機(jī)(英語(yǔ):finite-state machine虱歪,縮寫:FSM)又稱有限狀態(tài)自動(dòng)機(jī),簡(jiǎn)稱狀態(tài)機(jī)栅表,是表示有限個(gè)狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動(dòng)作等行為的數(shù)學(xué)模型笋鄙。

狀態(tài)機(jī)可歸納為4個(gè)要素,即現(xiàn)態(tài)怪瓶、條件萧落、動(dòng)作、次態(tài)洗贰≌裔“現(xiàn)態(tài)”和“條件”是因,“動(dòng)作”和“次態(tài)”是果:

  • 現(xiàn)態(tài):是指當(dāng)前所處的狀態(tài)哆姻。

  • 條件:又稱為“事件”宣增。當(dāng)一個(gè)條件被滿足,將會(huì)觸發(fā)一個(gè)動(dòng)作矛缨,或者執(zhí)行一次狀態(tài)的遷移爹脾。

  • 動(dòng)作:條件滿足后執(zhí)行的動(dòng)作帖旨。動(dòng)作執(zhí)行完畢后,可以遷移到新的狀態(tài)灵妨,也可以仍舊保持原狀態(tài)解阅。動(dòng)作不是必需的,當(dāng)條件滿足后泌霍,也可以不執(zhí)行任何動(dòng)作货抄,直接遷移到新狀態(tài)。

  • 次態(tài):條件滿足后要遷往的新狀態(tài)朱转⌒返兀“次態(tài)”是相對(duì)于“現(xiàn)態(tài)”而言的,“次態(tài)”一旦被激活藤为,就轉(zhuǎn)變成新的“現(xiàn)態(tài)”了怪与。

不是太好理解,我也是copy網(wǎng)上的概念缅疟,我們下面會(huì)舉例子說明分别。

什么是狀態(tài)模式

image.png
  • Context(環(huán)境類)

環(huán)境類又稱為上下文類,它是擁有多種狀態(tài)的對(duì)象存淫。由于環(huán)境類的狀態(tài)存在多樣性且在不同狀態(tài)下對(duì)象的行為有所不同耘斩,因此將狀態(tài)獨(dú)立出去形成單獨(dú)的狀態(tài)類。在環(huán)境類中維護(hù)一個(gè)抽象狀態(tài)類State的實(shí)例桅咆,這個(gè)實(shí)例定義當(dāng)前狀態(tài)括授,在具體實(shí)現(xiàn)時(shí),它是一個(gè)State子類的對(duì)象轧邪。

  • State(抽象狀態(tài)類)

它用于定義一個(gè)接口以封裝與環(huán)境類的一個(gè)特定狀態(tài)相關(guān)的行為刽脖,在抽象狀態(tài)類中聲明了各種不同狀態(tài)對(duì)應(yīng)的方法,而在其子類中實(shí)現(xiàn)類這些方法忌愚,由于不同狀態(tài)下對(duì)象的行為可能不同,因此在不同子類中方法的實(shí)現(xiàn)可能存在不同却邓,相同的方法可以寫在抽象狀態(tài)類中硕糊。

  • ConcreteState(具體狀態(tài)類)

它是抽象狀態(tài)類的子類,每一個(gè)子類實(shí)現(xiàn)一個(gè)與環(huán)境類的一個(gè)狀態(tài)相關(guān)的行為腊徙,每一個(gè)具體狀態(tài)類對(duì)應(yīng)環(huán)境的一個(gè)具體狀態(tài)简十,不同的具體狀態(tài)類其行為有所不同。

image.png

同樣的撬腾,下面會(huì)舉例說明螟蝙。

一個(gè)例子

假設(shè)現(xiàn)在有這么一個(gè)需求:給出一段java程序,要求刪除其中的注釋并返回刪除注釋之后的代碼民傻。

想想怎么去實(shí)現(xiàn)這個(gè)功能胰默?初步的思路是在一個(gè)while循環(huán)里面场斑,遍歷這個(gè)String,對(duì)每個(gè)字符進(jìn)行判斷牵署,然后是if else等等...功能肯定是可以實(shí)現(xiàn)的漏隐,但是我們有一個(gè)更加合適的套路,就是使用狀態(tài)機(jī)奴迅。

設(shè)計(jì)狀態(tài)機(jī)如下:

  1. 設(shè)正常狀態(tài)為0青责,并且初始為正常狀態(tài)

每遍歷一個(gè)字符,就依次檢查下列條件取具,若成立或全部檢查完畢脖隶,則回到這里檢查下一個(gè)字符

  1. 狀態(tài)0中遇到/,說明可能會(huì)遇到注釋暇检,則進(jìn)入狀態(tài)1          例子: int a = b; /

  2. 狀態(tài)1中遇到/浩村,說明進(jìn)入單行注釋部分,則進(jìn)入狀態(tài)2         例子: int a = b; //

  3. 狀態(tài)1中遇到占哟,說明進(jìn)入多行注釋部分心墅,則進(jìn)入狀態(tài)3         例子: int a= b; /

  4. 狀態(tài)1中沒有遇到*或/,說明/是路徑符號(hào)或除號(hào)榨乎,則恢復(fù)狀態(tài)0     例子: 8/3

  5. 狀態(tài)2中遇到回車符\n怎燥,說明單行注釋結(jié)束,則恢復(fù)狀態(tài)0      例子: int a = b; //hehe

  6. 狀態(tài)2中不是遇到回車符\n蜜暑,說明單行注釋還在繼續(xù)铐姚,則維持狀態(tài)2  例子: int a = b; //hehe

  7. 狀態(tài)3中遇到,說明多行注釋可能要結(jié)束肛捍,則進(jìn)入狀態(tài)4        例子: int a = b; /heh*

  8. 狀態(tài)3中不是遇到隐绵,說明多行注釋還在繼續(xù),則維持狀態(tài)3       例子: int a = b; /hehe

  9. 狀態(tài)4中遇到/拙毫,說明多行注釋要結(jié)束依许,則恢復(fù)狀態(tài)0          例子: int a = b; /hehe/

  10. 狀態(tài)4中不是遇到/,說明多行注釋只是遇到缀蹄,還要繼續(xù)峭跳,則恢復(fù)狀態(tài)3   例子: int a = b; /hehe*h

狀態(tài)圖:

image.png

if else實(shí)現(xiàn)狀態(tài)機(jī)

package space.kyu.mode.state;

public class CodeProcessor1 {
    private StringBuilder codeWithoutComment;
    private String originCode;

    public CodeProcessor1(String code) {
        originCode = code;
    }

    public String clearComment() {
        codeWithoutComment = new StringBuilder();
        char c, state;
        state = 0;
        for (int i = 0; i < originCode.length(); ++i) {
            c = getChar(i);
            if (state == 0) {
                if (c == '/') {
                    state = 1;
                } else {
                    putChar(c); // action
                }
            } else if (state == 1) {
                if (c == '/') // 例子: int a = b; //
                {
                    state = 2;

                } else if (c == '*') // 例子: int a= b; /*
                {
                    state = 3;
                } else // 例子: <common/md5.h> or 8/3
                {
                    state = 0;
                    putChar('/'); // action
                    putChar(c); // action
                }
            } else if (state == 2) {
                if (c == '\n') // 例子: int a = b; //hehe
                {
                    state = 0;
                    putChar(c); // action
                }
                // 例子: int a = b; //hehe
            } else if (state == 3) {
                if (c == '*') // 例子: int a = b; /*heh*
                {
                    state = 4;
                }
                // 例子: int a = b; /*hehe
            } else if (state == 4) {
                if (c == '/') // 例子: int a = b; /*hehe*/
                {
                    state = 0;
                } else // 例子: int a = b; /*hehe*h
                {
                    state = 3;
                }
            } else {
                System.out.println("state error!");
            }
        }
        return codeWithoutComment.toString();
    }

    private char getChar(int i) {
        return originCode.charAt(i);
    }

    private void putChar(char c) {
        codeWithoutComment.append(c);
    }
    
    public static void main(String[] args) {
        String code = " public static void main(String[] args) {" + "\n"
                + "    /*hehe " + "\n"
                + "      hehe " + "\n"
                + "     */ " + "\n"
                + "    /*hehe*/" + "\n"
                + "    int a, int b; " + "\n"
                + "    /* hehe */ " + "\n"
                + "    //hehe" + "\n"
                + "    a = 4+2; //hehe" + "\n"
                + "    b = a;" + "\n"
                + "    String file = \"/tmp/log.log\"" + "\n"
                + " }";
        System.out.println(code);
        System.out.println("*******************************");
        CodeProcessor1 process = new CodeProcessor1(code);
        String str = process.clearComment();
        System.out.println(str);
    }
}

輸出結(jié)果:

public static void main(String[] args) {
    /*hehe 
      hehe 
     */ 
    /*hehe*/
    int a, int b; 
    /* hehe */ 
    //hehe
    a = 4+2; //hehe
    b = a;
    String file = "/tmp/log.log"
 }
*******************************
 public static void main(String[] args) {
     
    
    int a, int b; 
     
    
    a = 4+2; 
    b = a;
    String file = "/tmp/log.log"
 }

狀態(tài)模式實(shí)現(xiàn)狀態(tài)機(jī)

package space.kyu.mode.state.interfac;

public class CodeProcessor2 {

    InputState currentState;
    StringBuilder codeWithoutComment;
    String originCode;

    public CodeProcessor2(String code) {
        originCode = code;
        currentState = new Normal();
    }

    public String clearComment() {
        codeWithoutComment = new StringBuilder();
        for (int i = 0; i < originCode.length(); ++i) {
            char charAt = getChar(i);
            currentState.handleInput(charAt, this);
        }
        return codeWithoutComment.toString();
    }

    private char getChar(int i) {
        return originCode.charAt(i);
    }

    public void putChar(char c) {
        codeWithoutComment.append(c);
    }

    public static void main(String[] args) {
        String code = " public static void main(String[] args) {" + "\n"
                + "    /*hehe " + "\n"
                + "      hehe " + "\n"
                + "     */ " + "\n"
                + "    /*hehe*/" + "\n"
                + "    int a, int b; " + "\n"
                + "    /* hehe */ " + "\n"
                + "    //hehe" + "\n"
                + "    a = 4+2; //hehe" + "\n"
                + "    b = a;" + "\n"
                + "    String file = \"/tmp/log.log\"" + "\n"
                + " }";
        System.out.println(code);
        System.out.println("*******************************");
        CodeProcessor2 process = new CodeProcessor2(code);
        String str = process.clearComment();
        System.out.println(str);
    }

}

abstract class InputState {
    protected char backslash = '/';
    protected char asterisk = '*';
    protected char lineBreaks = '\n';

    abstract void handleInput(char charAt, CodeProcessor2 processor);
}

class Normal extends InputState {
    @Override
    public void handleInput(char charAt, CodeProcessor2 processor) {
        if (charAt == backslash) {
            processor.currentState = new CommentSymbol();
        } else {
            processor.putChar(charAt);
        }
    }
}

class CommentSymbol extends InputState {
    @Override
    public void handleInput(char charAt, CodeProcessor2 processor) {
        if (charAt == backslash) {
            processor.currentState = new SinglelineComment();
        } else if (charAt == asterisk) {
            processor.currentState = new MutilineComment();
        } else {
            processor.putChar('/');
            processor.putChar(charAt);
            processor.currentState = new Normal();
        }
    }
}

class SinglelineComment extends InputState {
    @Override
    public void handleInput(char charAt, CodeProcessor2 processor) {
        if (charAt == lineBreaks) {
            processor.putChar(charAt);
            processor.currentState = new Normal();
        }
    }
}

class MutilineComment extends InputState {
    @Override
    public void handleInput(char charAt, CodeProcessor2 processor) {
        if (charAt == asterisk) {
            processor.currentState = new MutilineCommentEnding();
        }
    }
}

class MutilineCommentEnding extends InputState {
    @Override
    public void handleInput(char charAt, CodeProcessor2 processor) {
        if (charAt == backslash) {
            processor.currentState = new Normal();
        } else {
            processor.currentState = new MutilineComment();
        }
    }
}

其中:

CodeProcessor2 為 Context(環(huán)境類)

InputState 為 State(抽象狀態(tài)類)

Normal等繼承了InputState的類 為 ConcreteState(具體狀態(tài)類)

輸出結(jié)果:

public static void main(String[] args) {
    /*hehe 
      hehe 
     */ 
    /*hehe*/
    int a, int b; 
    /* hehe */ 
    //hehe
    a = 4+2; //hehe
    b = a;
    String file = "/tmp/log.log"
 }
*******************************
 public static void main(String[] args) {
     
    
    int a, int b; 
     
    
    a = 4+2; 
    b = a;
    String file = "/tmp/log.log"
 }

enum實(shí)現(xiàn)狀態(tài)機(jī)

利用java中提供的enum實(shí)現(xiàn)狀態(tài)機(jī)也是狀態(tài)模式的一種,這樣讓代碼更整潔并且不會(huì)產(chǎn)生很多的類導(dǎo)致類膨脹缺前。

package space.kyu.mode.state;

public class CodeProcessor {
        InputState currentState;
        StringBuilder codeWithoutComment;
        String originCode;
        public CodeProcessor(String code) {
            originCode = code;
            currentState = States.NORMAL;
        }
        
        public String clearComment() {
            codeWithoutComment = new StringBuilder();
            for(int i = 0; i < originCode.length(); ++i){
                char charAt = getChar(i);
                currentState.handleInput(charAt, this);
            }
            return codeWithoutComment.toString();
        }
        
        private char getChar(int i) {
            return originCode.charAt(i);
        }
        
        public void putChar(char c){
            codeWithoutComment.append(c);
        }
        
        public static void main(String[] args) {
            String code = " public static void main(String[] args) {" + "\n"
                    + "    /*hehe " + "\n"
                    + "      hehe " + "\n"
                    + "     */ " + "\n"
                    + "    /*hehe*/" + "\n"
                    + "    int a, int b; " + "\n"
                    + "    /* hehe */ " + "\n"
                    + "    //hehe" + "\n"
                    + "    a = 4+2; //hehe" + "\n"
                    + "    b = a;" + "\n"
                    + "    String file = \"/tmp/log.log\"" + "\n"
                    + " }";
            System.out.println(code);
            System.out.println("*******************************");
            CodeProcessor process = new CodeProcessor(code);
            String str = process.clearComment();
            System.out.println(str);
        }
    }

    interface InputState {
        void handleInput(char charAt, CodeProcessor processor);
    }

    enum States implements InputState {
        /**
         * 正常狀態(tài)
         */
        NORMAL{
            @Override
            public void handleInput(char charAt, CodeProcessor processor) {
                if (charAt == backslash) {
                    processor.currentState = COMMENT_SYMBOL;
                } else {
                    processor.putChar(charAt);
                }
            }
        },
        
        /**
         * 遇到注釋符 /
         */
        COMMENT_SYMBOL{
            @Override
            public void handleInput(char charAt, CodeProcessor processor) {
                if (charAt == backslash) {
                    processor.currentState = SINGLE_LINE_COMMENT;
                } else if (charAt == asterisk) {
                    processor.currentState = MUTI_LINE_COMMENT;
                } else {
                    processor.putChar('/');
                    processor.putChar(charAt);
                    processor.currentState = NORMAL;
                }
            }
        },
        
        /**
         * 進(jìn)入單行注釋
         */
        SINGLE_LINE_COMMENT{
            @Override
            public void handleInput(char charAt, CodeProcessor processor) {
                if (charAt == lineBreaks) {
                    processor.putChar(charAt);
                    processor.currentState = NORMAL;
                }
            }
        },
        
        /**
         * 進(jìn)入多行注釋
         */
        MUTI_LINE_COMMENT{
            @Override
            public void handleInput(char charAt, CodeProcessor processor) {
                if (charAt == asterisk) {
                    processor.currentState = MUTI_LINE_COMMENT_ENDDING;
                } 
            }
        },
        
        /**
         * 多行注釋 遇到 *
         */
        MUTI_LINE_COMMENT_ENDDING{
            @Override
            public void handleInput(char charAt, CodeProcessor processor) {
                if (charAt == backslash) {
                    processor.currentState = NORMAL;
                } else {
                    processor.currentState = MUTI_LINE_COMMENT;
                }
            }
        };
        
        char backslash = '/';
        char asterisk = '*';
        char lineBreaks = '\n';
    }

輸出結(jié)果:

 public static void main(String[] args) {
    /*hehe 
      hehe 
     */ 
    /*hehe*/
    int a, int b; 
    /* hehe */ 
    //hehe
    a = 4+2; //hehe
    b = a;
    String file = "/tmp/log.log"
 }
*******************************
 public static void main(String[] args) {
     
    
    int a, int b; 
     
    
    a = 4+2; 
    b = a;
    String file = "/tmp/log.log"
 }

參考

有限狀態(tài)機(jī)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蛀醉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子衅码,更是在濱河造成了極大的恐慌拯刁,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逝段,死亡現(xiàn)場(chǎng)離奇詭異垛玻,居然都是意外死亡割捅,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門夭谤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來棺牧,“玉大人,你說我怎么就攤上這事朗儒〖粘耍” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵醉锄,是天一觀的道長(zhǎng)乏悄。 經(jīng)常有香客問我,道長(zhǎng)恳不,這世上最難降的妖魔是什么檩小? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮烟勋,結(jié)果婚禮上规求,老公的妹妹穿的比我還像新娘。我一直安慰自己卵惦,他們只是感情好阻肿,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沮尿,像睡著了一般丛塌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上畜疾,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天赴邻,我揣著相機(jī)與錄音,去河邊找鬼啡捶。 笑死姥敛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的届慈。 我是一名探鬼主播徒溪,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼金顿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鲤桥,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤揍拆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后茶凳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嫂拴,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡播揪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了筒狠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猪狈。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖辩恼,靈堂內(nèi)的尸體忽然破棺而出雇庙,到底是詐尸還是另有隱情,我是刑警寧澤灶伊,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布疆前,位于F島的核電站,受9級(jí)特大地震影響聘萨,放射性物質(zhì)發(fā)生泄漏竹椒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一米辐、第九天 我趴在偏房一處隱蔽的房頂上張望胸完。 院中可真熱鬧,春花似錦翘贮、人聲如沸赊窥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)誓琼。三九已至,卻和暖如春肴捉,著一層夾襖步出監(jiān)牢的瞬間腹侣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工齿穗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留傲隶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓窃页,卻偏偏與公主長(zhǎng)得像跺株,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子脖卖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容