字典設計
修改時間
文章可能會隨時修改,慢慢完善。
2018-11-25 15:00
前言
遊戲服務器在啓動時,需要加載某些靜態數據到內存之中,比如所有商品的配置數據(可能包含id、名稱、價格等屬性)。
這些靜態數據在被加載後,會在內存中以某種形式呈現、組織和使用,在這裏假設稱它爲"信息"。
見過很多種關於這種"信息"的命名,比如配置信息(config)、模型數據(model)、模板數據(template)、定義數據(def)。
在這些年中經歷了以上的命名,最後還是選擇了字典條目(dict)作爲這種"信息"的命名。
數據載體
承載這些靜態數據的載體有很多種,在選擇上可能會基於開發語言或是團隊喜好(方便、直觀等)。
- 基於代碼的方式,將數據組織成語言代碼,比如lua、json、pike等。
- 基於excel,直接使用excel來保存數據。
- 基於數據庫表。
- 基於文本文件,比如csv、lua、json等。
術語
entry:字典條目,一般來說,一個字典條目對應一條記錄。
dict:字典,某一類型的所有字典條目組成了一部字典,因此係統中很有可能會存在多部字典。
加載過程
當在系統中使用字典條目時,我們希望能直接獲取和使用,不需要自己再做二次解析、轉換等繁瑣步驟。
爲了達到以上目的,一般來說我們會將字典的加載過程分爲兩個步驟:
- 讀取數據,可以認爲是字典條目的處理。
- 組織、校驗,可以認爲是字典的內部結構組織和校驗。
一、讀取數據
- 從數據載體中讀取數據記錄,並將每條記錄轉換成對應的字典條目。
- 對記錄中的特殊字段做解析,比如某些字符串其實是數組、鍵值對、枚舉等。也就是說,字典條目中的字段完全是存放解析之後的數據,沒有存放臨時數據的字段。
- 對字典條目中的某些字段做校驗,比如最小值、最大值、非空等。
- 對記錄中某些字段的解析,是有先後順序的,比如有些字段的值依賴於其它字段的值。
- 以上步驟應該都是自動完成的。
二、組織、校驗
- 對字典的內部結構進行組織。字典的內部結構可能是map(可能有順序),也可能是array或是table,或者是其它結構。使用這些結構是爲了更方便的獲取字典條目。
- 定義特殊的獲取字典條目的方法。比如我想直接獲取前三名的獎勵配置,只需要調用這個特殊方法就可以取到,不需要取出這三個字典條目,然後分別讀獎勵配置字段。
- 字典與字典之間可能是有依賴關係的,可能會使用其它字典條目。
- 字典整體的校驗、處理。
- 以上步驟會有些部分重合。
FAQ
Q: 字典是放在各個模塊中,還是統一存放在字典模塊中?
A: 目前傾向放到字典模塊中。字典並不涉及到特定業務邏輯,並且一個模塊可能需要使用不同功能的字典數據。
Q: 使用哪種數據載體存放字典文件比較合適?
A: 這個還真得根據團隊來看,目前在遊戲服比較喜歡使用文本格式,查看比較直觀,容易追蹤版本變化,缺點是可能得寫一套專門的導出工具。
Q: 怎麼校驗字典條目中某些字段的值?
A: 自己手寫代碼檢查太費勁,可以使用註解(比如javax.validation),統一校驗。
代碼片段
僅僅用作參考演示。
/**
* 數據字典基類。
*/
public abstract class Dict<T extends Entry> {
/** 存放原始字典數據條目,不要直接使用它。 */
protected TreeMap<Integer, T> primitiveEntries;
/** 賦值處理,將數據中的每個值賦值給字典條目中的字段。 */
private Map<Integer, Assignment> assignments;
/** 生成字典條目實例。 */
public T newEntry() { ... }
/** 待解析的數據字典文件。 */
public abstract String getFilenames();
/** 加載數據。 */
public boolean load() { ... }
/** 校驗、組織。 */
public abstract boolean process()
...
}
/**
* 數據字典條目基類。
*/
public abstract class Entry {
/** 唯一標識。 */
public int id;
/** 有些列需要先被處理。 */
public String[] getFirstColumns() { ... }
/** 有些列需要後被處理。 */
public String[] getLastColumns() { ... }
}
=======以下是具體的字典例子=======。
/**
* 通過使用註解來定義字段校驗。
*/
public class BattlePositionEntry extends Entry {
/** 站位1。 */
@Min(value = 1)
public int num1;
/** 站位2。 */
public int num2;
}
/**
* 這裏存放字典條目的內部數據結構並不是map,而是一個數組。
*/
public class BattlePositionDict extends Dict<BattlePositionEntry> {
private volatile int[][] positions;
public boolean process() { ... }
public int[][] getPositions() { ... }
}