最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java使用枚舉實(shí)現(xiàn)狀態(tài)機(jī)的方法詳解

 更新時(shí)間:2023年11月09日 10:30:25   作者:一只小熊貓呀  
這篇文章主要介紹了Java使用枚舉實(shí)現(xiàn)狀態(tài)機(jī)的方法詳解,枚舉類型很適合用來實(shí)現(xiàn)狀態(tài)機(jī),狀態(tài)機(jī)可以處于有限數(shù)量的特定狀態(tài),它們通常根據(jù)輸入,從一個(gè)狀態(tài)移動(dòng)到下一個(gè)狀態(tài),但同時(shí)也會存在瞬態(tài),需要的朋友可以參考下

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

枚舉類型很適合用來實(shí)現(xiàn)狀態(tài)機(jī)。狀態(tài)機(jī)可以處于有限數(shù)量的特定狀態(tài)。它們通常根據(jù)輸入,從一個(gè)狀態(tài)移動(dòng)到下一個(gè)狀態(tài),但同時(shí)也會存在瞬態(tài)。當(dāng)任務(wù)執(zhí)行完畢后,狀態(tài)機(jī)會立即跳出所有狀態(tài)。

每個(gè)狀態(tài)都有某些可接受的輸入,不同的輸入會使?fàn)顟B(tài)機(jī)從當(dāng)前狀態(tài)切換到新的狀態(tài)。由于枚舉限制了可能出現(xiàn)的狀態(tài)集大?。礌顟B(tài)數(shù)量),因此很適合表達(dá)(枚舉)不同的狀態(tài)和輸入。

每種狀態(tài)一般也會有某種對應(yīng)的輸出。

自動(dòng)售貨機(jī)是個(gè)很好的狀態(tài)機(jī)應(yīng)用的例子。首先,在一個(gè)枚舉中定義一系列輸入:

Input.java

import java.util.Random;

public enum Input {
    NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100),
    TOOTHPASTE(200), CHIPS(75), SODA(100), SOAP(50),
    ABORT_TRANSACTION {
        @Override
        public int amount() { // Disallow
            throw new RuntimeException("ABORT.amount()");
        }
    },
    STOP { // 這必須是最后一個(gè)實(shí)例
        @Override
        public int amount() { // 不允許
            throw new RuntimeException("SHUT_DOWN.amount()");
        }
    };
    int value; // 單位為美分(cents)

    Input(int value) {
        this.value = value;
    }

    Input() {
    }

    int amount() {
        return value;
    }

    ; // In cents
    static Random rand = new Random(47);

    public static Input randomSelection() {
        //不包括 STOP:
        return values()[rand.nextInt(values().length - 1)];
    }
}

注意其中兩個(gè) Input 有著對應(yīng)的金額,所以在接口中定義了 amount() 方法。然而,對另外兩個(gè) Input 調(diào)用 amount() 是不合適的,如果調(diào)用就會拋出異常。盡管這是個(gè)有點(diǎn)奇怪的機(jī)制(在接口中定義一個(gè)方法,然后如果在某些具體實(shí)現(xiàn)中調(diào)用它的話就會拋出異常),但這是枚舉的限制所導(dǎo)致的。

VendingMachine(自動(dòng)售貨機(jī))接收到輸入后,首先通過 Category(類別) 枚舉來對這些輸入進(jìn)行分類,這樣就可以在各個(gè)類別間切換了。下例演示了枚舉是如何使代碼變得更清晰、更易于管理的。

VendingMachine.java

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;

enum Category {
    MONEY(Input.NICKEL, Input.DIME, Input.QUARTER, Input.DOLLAR),
    ITEM_SELECTION(Input.TOOTHPASTE, Input.CHIPS, Input.SODA, Input.SOAP),
    QUIT_TRANSACTION(Input.ABORT_TRANSACTION),
    SHUT_DOWN(Input.STOP);
    private Input[] values;

    Category(Input... types) {
        values = types;
    }

    private static EnumMap<Input, Category> categories = new EnumMap<>(Input.class);

    static {
        for (Category c : Category.class.getEnumConstants()) {
            for (Input type : c.values) {
                categories.put(type, c);
            }
        }
    }

    public static Category categorize(Input input) {
        return categories.get(input);
    }
}

public class VendingMachine {
    private static State state = State.RESTING;
    private static int amount = 0;
    private static Input selection = null;

    enum StateDuration {TRANSIENT} // 標(biāo)識 enum

    enum State {
        RESTING {
            @Override
            void next(Input input) {
                switch (Category.categorize(input)) {
                    case MONEY:
                        amount += input.amount();
                        state = ADDING_MONEY;
                        break;
                    case SHUT_DOWN:
                        state = TERMINAL;
                    default:
                }
            }
        },
        ADDING_MONEY {
            @Override
            void next(Input input) {
                switch (Category.categorize(input)) {
                    case MONEY:
                        amount += input.amount();
                        break;
                    case ITEM_SELECTION:
                        selection = input;
                        if (amount < selection.amount()) {
                            System.out.println(
                                    "Insufficient money for " + selection);
                        } else {
                            state = DISPENSING;
                        }
                        break;
                    case QUIT_TRANSACTION:
                        state = GIVING_CHANGE;
                        break;
                    case SHUT_DOWN:
                        state = TERMINAL;
                    default:
                }
            }
        },
        DISPENSING(StateDuration.TRANSIENT) {
            @Override
            void next() {
                System.out.println("here is your " + selection);
                amount -= selection.amount();
                state = GIVING_CHANGE;
            }
        },
        GIVING_CHANGE(StateDuration.TRANSIENT) {
            @Override
            void next() {
                if (amount > 0) {
                    System.out.println("Your change: " + amount);
                    amount = 0;
                }
                state = RESTING;
            }
        },
        TERMINAL {
            @Override
            void output() {
                System.out.println("Halted");
            }
        };
        private boolean isTransient = false;

        State() {
        }

        State(StateDuration trans) {
            isTransient = true;
        }

        void next(Input input) {
            throw new RuntimeException("Only call " + "next(Input input) for non-transient states");
        }

        void next() {
            throw new RuntimeException("Only call next() for " + "StateDuration.TRANSIENT states");
        }

        void output() {
            System.out.println(amount);
        }
    }

    static void run(Supplier<Input> gen) {
        while (state != State.TERMINAL) {
            state.next(gen.get());
            while (state.isTransient) {
                state.next();
            }
            state.output();
        }
    }

    public static void main(String[] args) {
        Supplier<Input> gen = new RandomInputSupplier();
        if (args.length == 1) {
            gen = new FileInputSupplier(args[0]);
        }
        run(gen);
    }
}

// 基本的穩(wěn)健性檢查:
class RandomInputSupplier implements Supplier<Input> {
    @Override
    public Input get() {
        return Input.randomSelection();
    }
}

// 從以“;”分割的字符串的文件創(chuàng)建輸入
class FileInputSupplier implements Supplier<Input> {
    private Iterator<String> input;

    FileInputSupplier(String fileName) {
        try {
            input = Files.lines(Paths.get(fileName))
                    .skip(1) // Skip the comment line
                    .flatMap(s -> Arrays.stream(s.split(";")))
                    .map(String::trim)
                    .collect(Collectors.toList())
                    .iterator();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Input get() {
        if (!input.hasNext()) {
            return null;
        }
        return Enum.valueOf(Input.class, input.next().trim());
    }
}

下面是用于生成輸出的文本文件:

VendingMachine.txt

QUARTER;QUARTER;QUARTER;CHIPS;
DOLLAR;DOLLAR;TOOTHPASTE;
QUARTER;DIME;ABORT_TRANSACTION;
QUARTER;DIME;SODA;
QUARTER;DIME;NICKEL;SODA;
ABORT_TRANSACTION;
STOP;

以下是運(yùn)行參數(shù)配置:

在這里插入圖片描述

運(yùn)行結(jié)果如下:

在這里插入圖片描述

因?yàn)橥ㄟ^ switch 語句在枚舉實(shí)例中進(jìn)行選擇操作是最常見的方式(注意,為了使 switch 便于操作枚舉,語言層面需要付出額外的代價(jià)),所以在組織多個(gè)枚舉類型時(shí),最常問的問題之一就是“我需要什么東西之上(即以什么粒度)進(jìn)行 switch”。這里最簡單的辦法是,回頭梳理一遍 VendingMachine,就會發(fā)現(xiàn)在每種 State 下,你需要針對輸入操作的基本類別進(jìn)行 switch 操作:投入錢幣、選擇商品、退出交易、關(guān)閉機(jī)器。并且在這些類別內(nèi),你還可以投入不同類別的貨幣,選擇不同類別的商品。Category 枚舉會對不同的 Input 類型進(jìn)行分類,因此 categorize() 方法可以在 switch 中生成恰當(dāng)?shù)?Category。這種方法用一個(gè) EnumMap 實(shí)現(xiàn)了高效且安全的查詢。

如果你研究一下 VendingMachine 類,便會發(fā)現(xiàn)每個(gè)狀態(tài)的區(qū)別,以及對輸入的響應(yīng)區(qū)別。同時(shí)還要注意那兩個(gè)瞬態(tài):在 run() 方法中,售貨機(jī)等待一個(gè) Input,并且會一直在狀態(tài)間移動(dòng),直到它不再處于某個(gè)瞬態(tài)中。

VendingMachine 可以通過兩種不同的 Supplier 對象,以兩種方法來測試。RandomInputSupplier 只需要持續(xù)生成除 SHUT_DOWN 以外的任何輸入。通過一段較長時(shí)間的運(yùn)行后,就相當(dāng)于做了一次健康檢查,以確定售貨機(jī)不會偏離到某些無效狀態(tài)。FileInputSupplier 接收文本形式的輸入描述文件,并將它們轉(zhuǎn)換為 enum 實(shí)例,然后創(chuàng)建 Input 對象。下面是用于生成以上輸出的文本文件:

FileInputSupplier 的構(gòu)造器將這個(gè)文件轉(zhuǎn)換為行級的 Stream 流,并忽略注釋行。然后它通過 String.split() 方法將每一行都根據(jù)分號拆開。這樣就能生成一個(gè)字符串?dāng)?shù)組,可以通過先將該數(shù)組轉(zhuǎn)化為 Stream,然后執(zhí)行 flatMap(),來將其注入(前面 FileInputSupplier 中生成的)Stream 中。結(jié)果將刪除所有的空格,并轉(zhuǎn)換為 List,并從中得到 Iterator。

上述設(shè)計(jì)有個(gè)限制:VendingMachine 中會被 State 枚舉實(shí)例訪問到的字段都必須是靜態(tài)的,這意味著只能存在一個(gè) VendingMachine 實(shí)例。這可能不會是個(gè)大問題——你可以想想一個(gè)實(shí)際的(嵌入式Java)實(shí)現(xiàn),每臺機(jī)器可能就只有一個(gè)應(yīng)用程序。

到此這篇關(guān)于Java使用枚舉實(shí)現(xiàn)狀態(tài)機(jī)的方法詳解的文章就介紹到這了,更多相關(guān)Java枚舉實(shí)現(xiàn)狀態(tài)機(jī)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

汨罗市| 赣榆县| 宁乡县| 文水县| 长乐市| 马公市| 高要市| 宁远县| 西安市| 塔城市| 凌源市| 江都市| 新野县| 专栏| 平潭县| 敖汉旗| 麟游县| 武穴市| 简阳市| 珲春市| 岳西县| 榆社县| 福泉市| 北票市| 岳池县| 汤阴县| 扬州市| 安乡县| 榆树市| 高台县| 武陟县| 梁平县| 湖州市| 班玛县| 新野县| 金川县| 秦皇岛市| 扎赉特旗| 胶南市| 潞城市| 太仆寺旗|