[Java] 嘗試詞法分析器的總結

寫在前面

因爲我對寫解析器只有 閱讀了幾篇文章 的知識量,所以水平並非很高,此文權當一次我的總結,沒法保證所涉及的知識點、思路徹底無誤,若有錯誤,還請各位大佬指正。java

從一個正整數表達式開始

這篇文章圍繞的僅僅是一個 正整數表達式,並且它很簡單,不會出現括號嵌套等狀況,咱們的目標只是把express

10 * 5 + 1

解析爲一個 Token 序列,以下:json

[
    { type: NUMBER,   value: `10` },
    { type: OPERATOR, value: `*`  },
    { type: NUMBER,   value: `5`  },
    { type: OPERATOR, value: `+`  },
    { type: NUMBER,   value: `1`  }
]

我習慣從簡單的開始,那麼咱們先從一個最簡單的、只有個位數、沒有空格的式子開始:app

1+1

最簡單的思路

其實詞法分析器要作的事本質上很簡單:對輸入的字符串進行遍歷,分割成有意義的 Token
所以,最簡單的思路就是一個 for 循環:ide

String expression = "1+1";
for (char ch : expression.toCharArray()) {
    // 在這裏進行處理
}

因此咱們定義一個 Scanner,爲了後續方便,順手實現個簡單的單例吧:工具

public class Scanner {
  
      private static volatile Scanner instance;

    public static Scanner getInstance() {
          if (Scanner.instance == null) {
              synchronized ( Scanner.class ) {
                  if (Scanner.instance == null) {
                    Scanner.instance = new Scanner();
                }
            }
        }
        return Scanner.instance;
    }

    private String expression;

    public Scanner from(String expression) {
        this.expression = expression;
        return this;
    }

      public void process() {
        for (char ch : expression.toCharArray()) {
            // 在這裏進行處理
        }
    }
  
     public static void main(String ... args) {
        Scanner scanner = Scanner.getInstance().from("1+1");
          scanner.process();
    }
}

定義 Token 類型

在當前的 1+1 表達式中,涉及到的 Token 很少,只有數字、操做符,所以用一個枚舉類便可表述:測試

public enum Type {
    INIT,
    NUMBER,
    OPERATOR,
    UNKNOWN;
    
    public static Type of(char ch) {
        if ('0' <= ch && ch <= '9') {
            return NUMBER;
        }
        if ("+-*/".indexOf(ch) != -1) {
            return OPERATOR;
        }
        return UNKNOWN;
    }
}

同時該枚舉類承擔辨識字符類型的工做:this

Type.of('1') // NUMBER
Type.of('+') // OPERATOR
Type.of('a') // UNKNOWN

定義 Token

public class Token {
    // 一個 Token 的類型一旦肯定,就不可能再改變。
    private final Type type;
    // 用以存儲 Token 的值。
    private final StringBuffer value;
    
    public Token(Type type) {
        this.type = type;
        this.value = new StringBuffer();
    }
    
    public void appendValue(char ch) {
        this.value.append(ch);
    }
    
    public String getValue() {
        return this.value.toString();
    }
    
    public Type getType() {
        return this.type;
    }
    
    @Override
    public String toString() {
        return String.format("{type: %s, value: `%s`}",
            this.getType().name(),
            this.getValue());
    }
}

處理 1+1

public class Scanner {
      // 省略...  
      public void process() {
        for (char ch : expression.toCharArray()) {
            Type type = Type.of(ch);
            if (Type.UNKNOWN.equals(type)) {
                throw new RuntimeException(String.format("`%c` 並不屬於指望的字符類型", ch));
            }
            Token token = new Token(type);
            token.appendValue(ch);
            System.out.println(token);
        }
    }
  
     public static void main(String ... args) {
        Scanner scanner = new Scanner("1+1");
          scanner.process();
    }
}

/** 輸出
 * {type: NUMBER,   value: `1`}
 * {type: OPERATOR, value: `+`}
 * {type: NUMBER,   value: `1`}
 */

如今來加點難度: 10+1

如今一個數字可能不止一位了,那麼咱們該怎麼辦呢?code

使用狀態圖:orm

┌-[ 0-9 ]-┐    ┌-[ +|-|*|/ ]-┐   ┌-[ 0-9 ]-┐
---(  NUMBER  )--- (  OPERATOR  )---(  NUMBER  )---

具體的理論這裏就不贅述了,有興趣能夠自行查閱相關資料,這裏簡單說一下怎麼用:

如今咱們來列個表,看一下對於 10+1,在狀態上有什麼變化:

字符 狀態 Token
NULL INIT NULL
1 NUMBER {id: 0, type: NUMBER, value: 1}
0 NUMBER {id: 0, type: NUMBER, value: 10}
+ OPERATOR {id: 1, type: OPERATOR, value: +}
1 NUMBER {id: 2, type: NUMBER, value: 1}

能夠看到,在讀到字符 10 時,狀態沒有發生變化,也就是說它們是一個總體(或是一個總體的一部分)。
若是在 0 後面還有其餘數字,那麼直到引發狀態改變的字符出現以前,這些字符就組成了整個 Token

同時,咱們還發現引入狀態圖後,有個有意思的事:

從 初始狀態 INIT 開始,咱們只容許後邊是 NUMBER 類型;
NUMBER 後邊容許 NUMBEROPERATOR 類型;
OPERATOR 後邊容許 NUMBER 類型。

除此以外的狀態都是不合法的,這也就是有時候解析類的包(好比 fast-json)會看到的 Invalid Character 錯誤的狀況。

因此咱們須要改改代碼,同時爲 Scanner 添加一個更新狀態的方法:

public class Scanner {
    // 省略
      private Token token;
  
      public void setToken(Token token) {
        this.token = token;
    }
      
      public void process() {
          // 初始化
          this.setToken(new Token(Type.INIT));
        for (char ch : expression.toCharArray()) {
            Type type = Type.of(ch);
            if (Type.UNKNOWN.equals(type)) {
                throw new RuntimeException(String.format("`%c` 並不屬於指望的字符類型", ch));
            }
    
            // 根據當前 Token 的類型,選擇不一樣的判斷分支
            switch (token.getType()) {
                case INIT:
                    switch (type) {
                        // 當前是初始狀態,遇到了數字, 切換狀態。
                        case NUMBER:
                            this.setToken(new Token(Type.NUMBER));
                            this.token.appendValue(ch);
                            break;
                        default:
                            throw new RuntimeException(String
                                                   .format("Invalid Character: `%c`", ch));
                    }
                    break;
                case NUMBER:
                    switch (type) {
                        // 當前是數字狀態,遇到了數字,追加字符。
                        case NUMBER:
                            this.token.appendValue(ch);
                            break;
                        // 當前是數字狀態,遇到了操做符,切換狀態。
                        case OPERATOR:
                            this.setToken(new Token(Type.OPERATOR));
                            this.token.appendValue(ch);
                            break;
                        default:
                            throw new RuntimeException(String
                                                   .format("Invalid Character: `%c`", ch));
                    }
                    break;
                case OPERATOR:
                    switch (type) {
                        // 當前是操做符狀態,遇到了數字,切換狀態。
                        case NUMBER:
                            this.setToken(new Token(Type.NUMBER));
                            this.token.appendValue(ch);
                            break;
                        default:
                            throw new RuntimeException(String
                                                   .format("Invalid Character: `%c`", ch));
                    }
                    break;
            }
            System.out.println(token);
        }
    }
}
/** 輸出
 * {type: NUMBER,   value: `1`}
 * {type: NUMBER,   value: `10`}
 * {type: OPERATOR, value: `+`}
 * {type: NUMBER,   value: `1`}
 */

試着簡化一下

咱們剛纔用了一個巨大無比的 switch 結構來描述狀態圖,如今咱們由內而外試着簡化這個巨無霸。

 先從內部開始:

switch (type) {
    // 當前是 ** 狀態,遇到了 ** , 執行 ** 操做。
    case NUMBER:
        // ...
        break;
    case ...
    default:
        throw new RuntimeException(String.format("Invalid Character: `%c`", ch));
}

其實稍微概括總結一下就能發現, 執行 ** 操做 這部分,總的來講只有兩種:

NewToken
    對應着
        {
            token = new Token(Type.NUMBER);
            token.appendValue(ch);
        }
AppendValue
    對應着
        {
            token.appendValue(ch);
        }

如今咱們再引入一個工具來幫助咱們簡化:表驅動。

其實從上面的對應關係不難發現,咱們能夠用 HashMap 來簡單模擬一個表,幫助咱們減小工做。

在此以前,咱們須要把上述關係中的 {操做} 部分用一個接口來解耦:

public interface Behavior {
    void apply(Token token, Type type, char ch);
}

而後咱們來定義一個枚舉類 Behaviors 來表示操做類型:

public enum Behaviors {
    NewToken,
      AppendValue;

      private static final Scanner scanner;
    private static final HashMap<Behaviors, Behavior> behaviorMap;

    static {
          scanner = Scanner.getInstance();
        behaviorMap = new HashMap<>();
        behaviorMap.put(Behaviors.NewToken, (token, type, ch) -> {
            token = new Token(type);
            token.appendValue(ch);
              scanner.setToken(token);
        });
        behaviorMap.put(Behaviors.AppendValue, (token, type, ch) -> {
            token.appendValue(ch);
        });
    }

    public void apply(Token token, Type type, char ch) {
        behaviorMap.get(this)
                .apply(token, type, ch);
    }
}

那麼如今 執行 操做 這部分,如今能夠用 HashMap 來表述了:

// 根據當前 Token 的類型,選擇不一樣的判斷分支
switch (token.getType()) {
    case INIT:
        HashMap<Type, Behaviros> behaviorsMap = new HashMap<>();
        // 當前是初始狀態,遇到了數字, 切換狀態。
        behaviorsMap.put(Type.NUMBER, Behaviors.NewToken);
        break;
      case NUMBER:
           HashMap<Type, Behaviros> behaviorsMap = new HashMap<>();
        // 當前是數字狀態,遇到了數字,追加字符。
        behaviorsMap.put(Type.NUMBER, Behaviors.AppendValue);
        // 當前是數字狀態,遇到了操做符,切換狀態。
        behaviorsMap.put(Type.Operator, Behaviors.NewToken);
         break;
    case OPERATOR:
        HashMap<Type, Behaviros> behaviorsMap = new HashMap<>();
        // 當前是操做符狀態,遇到了數字,切換狀態。
        behaviorsMap.put(Type.NUMBER, Behaviors.NewToken);
        break;
}

既然是 Java ,那麼讓咱們來讓這部分看起來 OO 一些:

public class BehaviorMap {
    private final HashMap<Type, Behaviors> map;
      
      public BehaviorMap() {
        this.map = new HashMap();
    }
      
      public BehaviorMap at(Type type, Behaviors behaviors) {
        this.map.put(type, behaviors);
          return this;
    }
  
     public BehaviorsTable done() {
          return BehaviorsTable.getInstance();
    }
}

如今再來看看:

// 根據當前 Token 的類型,選擇不一樣的判斷分支
switch (token.getType()) {
    case INIT:
        BehaviorMap map = new BehaviorMap();
        map
               // 當前是初始狀態,遇到了數字, 切換狀態。
              .at(Type.NUMBER, Behaviors.NewToken);
        break;
      case NUMBER:
           BehaviorMap map = new BehaviorMap();
        map
              // 當前是數字狀態,遇到了數字,追加字符。
             .at(Type.NUMBER, Behaviors.AppendValue);
            // 當前是數字狀態,遇到了操做符,切換狀態。
            .at(Type.Operator, Behaviors.NewToken);
         break;
    case OPERATOR:
        BehaviorMap map = new BehaviorMap();
        map
              // 當前是操做符狀態,遇到了數字,切換狀態。
              .at(Type.NUMBER, Behaviors.NewToken);
        break;
}

簡化外部

如今咱們能夠看到表驅動對於消除判斷分支的威力了,那麼咱們能夠用一樣的方法將外部 switch 也消除掉:

public class BehaviorsTable {

    private static volatile BehaviorsTable instance;

    public static BehaviorsTable getInstance() {
        if (BehaviorsTable.instance == null) {
            synchronized ( BehaviorsTable.class ) {
                if (BehaviorsTable.instance == null) {
                    BehaviorsTable.instance = new BehaviorsTable();
                }
            }
        }
        return BehaviorsTable.instance;
    }

    private final HashMap<Type, BehaviorMap> map;

    public BehaviorsTable () {
        this.map = new HashMap<>();
    }

    public BehaviorMap register(Type type) {
        BehaviorMap behaviorMap = new BehaviorMap();
        this.map.put(type, behaviorMap);
        return behaviorMap;
    }
}

如今整個巨大的 switch 結構咱們就能夠簡化爲:

BehaviorsTable
    .getInstance()
    .register(Type.INIT)
        .at(Type.NUMBER, Behaviors.NewToken)
        .done()
    .register(Type.NUMBER)
          .at(Type.NUMBER, Behaviors.AppendValue)
          .at(Type.OPERATOR, Behaviors.NewToken)
          .done()
      .register(Type.OPERATOR)
          .at(Type.NUMBER, Behaviors.NewToken)
          .done();

如今 process 方法咱們就能夠簡化爲:

public void process() {
    this.setToken(new Token(Type.INIT));
    for (char ch : expression.toCharArray()) {
        Type type = Type.of(ch);
        if (Type.UNKNOWN.equals(type)) {
            throw new RuntimeException(String.format("`%c` 並不屬於指望的字符類型", ch));
        }
        BehaviorsTable
            .getInstance()
               // 根據當前 Token 類型獲取對應的處理對策
            .get(this.token.getType())
                  // 獲取當前字符所屬的處理行爲
                .is(type)
                .apply(type, ch);
        System.out.println(token);
    }
}
咱們在源碼裏作了一些改動,請參考文章底部所有代碼。

讓咱們測試一下

public static void main(String ... args) {
    Scanner scanner = Scanner.getInstance();
  
    scanner.from("10+1").process();
      /**
       * {type: NUMBER, value: `1`}
       * {type: NUMBER, value: `10`}
       * {type: NUMBER, value: `+`}
       * {type: NUMBER, value: `1`}
       */
  
      scanner.from("10 +1").process();
       /**
       * {type: NUMBER, value: `1`}
       * {type: NUMBER, value: `10`}
       * Exception in thread "main" java.lang.RuntimeException: ` ` 並不屬於指望的字符類型
       */
      
      scanner.from("10++1").process();
      /**
       * {type: NUMBER, value: `1`}
       * {type: NUMBER, value: `10`}
       * {type: OPERATOR, value: `+`}
    * Exception in thread "main" java.lang.RuntimeException: Invalid Character: `+` for Token `OPERATOR`
       */
}

如今看起來一切正常,可是別忘了 Scanner 的工做是將輸入的字符串分割爲 Token 序列,所以咱們須要讓 process 方法返回處理後的 LinkedList<Token> 序列。

爲此咱們須要將每次新生成的 Token 保存下來:

public class Scanner {
    private LinkedList<Token> tokens;
      private Token token;
  
      public void addToken(Token token) {
        this.token = token;
          this.tokens.add(token);
    }
  
       public LinkedList<Token> process() {
        this.setToken(new Token(Type.INIT));
        for (char ch : expression.toCharArray()) {
            Type type = Type.of(ch);
            if (Type.UNKNOWN.equals(type)) {
                throw new RuntimeException(String.format("`%c` 並不屬於指望的字符類型", ch));
            }
            BehaviorsTable
                    .getInstance()
                    .get(this.token.getType())
                    .is(type)
                    .apply(type, ch);
        }
        return this.tokens;
    }
  
       public static void main(String ... args) {
        Scanner scanner = Scanner.getInstance().from("10*5+1");
          LinkedList<Token> tokens = scanner.process();
          for (Token token : tokens) {
              System.out.println(token);
        }
    }
}

記得將 Behaviors 初始化部分中 NewToken 裏的行爲修改一下:

behaviorMap.put(Behaviors.NewToken, (token, type, ch) -> {
            token = new Token(type);
            token.appendValue(ch);
              //scanner.setToken(token);
            scanner.addToken(token);
        });

如今再看看結果:

{type: NUMBER, value: `10`}
{type: OPERATOR, value: `*`}
{type: NUMBER, value: `5`}
{type: OPERATOR, value: `+`}
{type: NUMBER, value: `1`}

看起來一切都如咱們所願!如今離最初的目標只剩下空格的處理了,得益於咱們抽象了行爲 Behaviors,咱們只須要在 Type 中註冊空格,而後爲 BehaviorsTable 註冊各類類型下對空格的處理就好了:

public enum Type {
    SPACE;
      
      public Tpye of(char ch) {
        if (' ' == ch) return SPACE;
    }
}

public enum Behaviors {
    Continue;
  
      static {
        behaviorMap
              .put(Behaviors.Continue, (token, type, ch) -> {
                // 留空就好了
            })
    }
}

public class Scanner {
       static {
        BehaviorsTable
                .getInstance()
                .register(Type.INIT)
                    .at(Type.NUMBER, Behaviors.NewToken)
                       .at(Type.SPACE, Behaviors.Continue)
                    .done()
                .register(Type.NUMBER)
                    .at(Type.NUMBER, Behaviors.AppendValue)
                    .at(Type.OPERATOR, Behaviors.NewToken)
                       .at(Type.SPACE, Behaviors.Continue)
                    .done()
                .register(Type.OPERATOR)
                    .at(Type.NUMBER, Behaviors.NewToken)
                       .at(Type.SPACE, Behaviors.Continue)
                    .done();
    }
}

還缺什麼呢

咱們如今完成的掃描器實際上沒法識別出 1 1 + 0 是個錯誤的表達式,它會解析出以下序列:

{type: NUMBER, value: 11}
{type: OPERATOR, value: +}
{type: NUMBER, value: 0}

我我的但願這部分工做往上層放,由消化 Token 序列的調用者經過模式匹配的方式去驗證,不過這樣的話,Type.SPACE 的處理就不能隨意 Continue 了,有興趣的話看官能夠自行嘗試一下 : P

所有代碼

一個小嚐試,就不傳 Github 了,直接放這兒吧 (其實就是懶...

Scanner.java

public class Scanner {
  
   private static volatile Scanner instance;

   public static Scanner getInstance() {
      if (Scanner.instance == null) {
         synchronized ( Scanner.class ) {
            if (Scanner.instance == null) {
               Scanner.instance = new Scanner();
            }
         }
      }
      return Scanner.instance;
   }

   static {
     // 註冊行爲表
      BehaviorsTable
            .getInstance()
            // 註冊 INIT 狀態的行爲表
            .register(Type.INIT)
               .at(Type.NUMBER, Behaviors.NewToken)
               .at(Type.SPACE, Behaviors.Continue)
               .done()
            .register(Type.NUMBER)
               .at(Type.NUMBER, Behaviors.AppendValue)
               .at(Type.OPERATOR, Behaviors.NewToken)
               .at(Type.SPACE, Behaviors.Continue)
               .done()
            .register(Type.OPERATOR)
               .at(Type.NUMBER, Behaviors.NewToken)
               .at(Type.SPACE, Behaviors.Continue)
               .done();
   }

   private String expression;
   private LinkedList<Token> tokens;
   private Token token;

   public Scanner from(String expression) {
      this.expression = expression;
      this.tokens = new LinkedList<>();
      return this;
   }
  
   public void setToken(Token token) {
      this.token = token;
   }

   public Token getToken ( ) {
      return token;
   }
  
   public LinkedList<Token> process() {
      this.setToken(new Token(Type.INIT));
      for (char ch : expression.toCharArray()) {
         Type type = Type.of(ch);
         if (Type.UNKNOWN.equals(type)) {
            throw new RuntimeException(String.format("`%c` 並不屬於指望的字符類型", ch));
         }
         BehaviorsTable
               .getInstance()
                   // 獲取當前 Token 類型所適用的行爲表
               .get(this.token.getType())
                       // 獲取當前字符所適用的行爲
                       .is(type)
                       .apply(type, ch);
      }
      return this.tokens;
    }

   public void addToken(Token token) {
     // 更新一下當前 Token
      this.token = token;
      this.tokens.add(token);
   }
  
   public static void main(String ... args) {
        Scanner scanner = Scanner.getInstance().from("10 * 5+1");
       LinkedList<Token> tokens = scanner.process();
       for (Token token : tokens) {
          System.out.println(token);
      }
    }
}

Type.java

// Token 類型枚舉
public enum Type {
    INIT,        // 初始化時使用
    SPACE,        // 空格
    NUMBER,        // 數字
    OPERATOR,    // 操做符
    UNKNOWN;    // 未知類型
    
    public static Type of(char ch) {
        if (' ' == ch) {
            return SPACE;
        }
        if ('0' <= ch && ch <= '9') {
            return NUMBER;
        }
        if ("+-*/".indexOf(ch) != -1) {
            return OPERATOR;
        }
        return UNKNOWN;
    }
}

Token.java

public class Token {
    // 一個 Token 的類型一旦肯定,就不可能再改變。
    private final Type type;
    // 用以存儲 Token 的值。
    private final StringBuffer value;
    
    public Token(Type type) {
        this.type = type;
        this.value = new StringBuffer();
    }
    
      // 向 value 中追加字符
    public void appendValue(char ch) {
        this.value.append(ch);
    }
    
    public String getValue() {
        return this.value.toString();
    }
    
    public Type getType() {
        return this.type;
    }
    
    @Override
    public String toString() {
        return String.format("{type: %s, value: `%s`}",
            this.getType().name(),
            this.getValue());
    }
}

Behavior.java

public interface Behavior {
    /**
     * 將行爲抽象出來
     * @param token 當前的 token
     * @param type 讀入字符的類型
     * @param ch 讀入的字符
     */
    void apply(Token token, Type type, char ch);
}

Behaviors.java

// 預設行爲
public enum Behaviors {
   NewToken,        // 新建一個指定類型的 Token, 將當前字符保存到新 Token
   AppendValue,        // 將當前字符追加到當前 Token 的值中
   Continue,        // 跳過當前字符
   InvalidCharacter; // 當前 Token 類型所不指望的字符類型,會拋出一個異常

      // 持有一個引用就不用總是調用 getInstance()。
   private static final Scanner scanner;
      // 爲預設行爲指定行爲內容
   private static final HashMap<Behaviors, Behavior> behaviorMap;

   static {
      scanner = Scanner.getInstance();
      behaviorMap = new HashMap<>();
     // 指定 NewToken,行爲邏輯參見枚舉值說明
      behaviorMap.put(Behaviors.NewToken, (token, type, ch) -> {
         token = new Token(type);
         token.appendValue(ch);
         scanner.addToken(token);
      });
     // 指定 AppendValue,行爲邏輯參見枚舉值說明
      behaviorMap.put(Behaviors.AppendValue, (token, type, ch) -> {
         token.appendValue(ch);
      });
     // 指定 Continue,行爲邏輯參見枚舉值說明
      behaviorMap.put(Behaviors.Continue, (token, type, ch) -> {});
     // 指定 InvalidCharacter,行爲邏輯參見枚舉值說明
      behaviorMap.put(Behaviors.InvalidCharacter, (token, type, ch) -> {
         throw new RuntimeException(String
                                    .format("Invalid Character: `%c` for Token `%s`",
                                            ch, token.getType().name()));
      });
   }

   public void apply(Type type, char ch) {
     // 獲取預設行爲
      behaviorMap.get(this)
            // 向行爲中傳遞當前 Token, 當前字符類型,當前字符
            .apply(scanner.getToken(), type, ch);
   }
}

BehaviorsMap.java

// 保存某一字符類須要執行何種預設行爲的映射關係
public class BehaviorsMap {
    private final HashMap<Type, Behaviors> map;
   public BehaviorsMap() {
        this.map = new HashMap();
    }
   
    /**
     * 註冊指定類型所需的預設行爲
     * @param type 指定類型
     * @param behaviors 指定所需的預設行爲
     */
   public BehaviorsMap at(Type type, Behaviors behaviors) {
        this.map.put(type, behaviors);
       return this;
    }

  // 註冊完後回退操做域到 BehaviorsTable
    public BehaviorsTable done() {
      return BehaviorsTable.getInstance();
   }

  // 獲取指定類型的預設行爲
   public Behaviors is (Type type) {
      Behaviors behaviors = this.map.get(type);
      if (behaviors == null) {
        // 若是沒有註冊,那麼使用 InvalidCharacter 預設行爲,由於出現了非預期的字符類型
         behaviors = Behaviors.InvalidCharacter;
      }
      return behaviors;
   }
}

BehaviorsTable.java

// 行爲表
public class BehaviorsTable {

    private static volatile BehaviorsTable instance;

    public static BehaviorsTable getInstance() {
        if (BehaviorsTable.instance == null) {
            synchronized ( BehaviorsTable.class ) {
                if (BehaviorsTable.instance == null) {
                    BehaviorsTable.instance = new BehaviorsTable();
                }
            }
        }
        return BehaviorsTable.instance;
    }

    private final HashMap<Type, BehaviorMap> map;

    public BehaviorsTable () {
        this.map = new HashMap<>();
    }

      // 註冊指定當前類型,返回一個空的 BehaviorsMap 來註冊預設行爲
    public BehaviorsMap register(Type type) {
        BehaviorsMap behaviorsMap = new BehaviorsMap();
        this.map.put(type, behaviorsMap);
        return behaviorsMap;
    }

   // 獲取指定當前類型的 BehaviorsMap
    public BehaviorsMap get(Type type) {
        return this.map.get(type);
    }
}
相關文章
相關標籤/搜索