目录
- 字典设计
- 修改时间
- 前言
- 数据载体
- 术语
- 加载过程
- 一、读取数据
- 二、组织、校验
- FAQ
- 代码片段
字典设计
修改时间
文章可能会随时修改,慢慢完善。
2018-12-04 16:00
前言
游戏服务器在启动时,需要加载某些静态数据到内存之中,比如所有商品的配置数据(可能包含id、名称、价格等属性)。
这些静态数据在被加载后,会在内存中以某种形式呈现、组织和使用,在这里假设称它为"信息"。
见过很多种关于这种"信息"的命名,比如配置信息(config)、模型数据(model)、模板数据(template)、定义数据(def)。
在这些年中经历了以上的命名,最后还是选择了字典条目(dict)作为这种"信息"的命名。
数据载体
承载这些静态数据的载体有很多种,在选择上可能会基于开发语言或是团队喜好(方便、直观等)。
- 基于代码的格式,将数据组织成语言代码,比如lua、json、pike等,部分代码可以做成一种压缩形式。
- 基于excel,直接使用excel来保存数据。
- 基于数据库表。
- 基于文本文件,比如csv、lua、json等。
- 自定义编辑器,可能存成二进制或是文本格式。
术语
entry:字典条目,一般来说,一个字典条目对应一条记录。
dict:字典,某一类型的所有字典条目组成了一部字典,因此系统中很有可能会存在多部字典。
加载过程
当在系统中使用字典条目时,我们希望能直接获取和使用,不需要自己再做二次解析、转换等繁琐步骤。
为了达到以上目的,一般来说我们会将字典的加载过程分为两个步骤:
- 读取数据,可以认为是字典条目的处理。
- 组织、校验,可以认为是字典的内部结构组织和校验。
一、读取数据
- 从数据载体中读取数据记录,并将每条记录转换成对应的字典条目。
- 对记录中的特殊字段做解析,比如某些字符串其实是数组、键值对、枚举等。也就是说,字典条目中的字段完全是存放解析之后的数据,没有存放临时数据的字段。
- 调用自定义setter方法,处理一些特殊解析。
- 对字典条目中的某些字段做校验,比如最小值、最大值、非空等。
- 对记录中某些字段的解析,是有先后顺序的,比如有些字段的值依赖于其它字段的值。
- 以上步骤应该都是自动完成的。
二、组织、校验
- 对字典的内部结构进行组织。字典的内部结构可能是map(可能有顺序),也可能是array、table、其它结构,甚至可以没有结构。使用字典是为了更方便的获取字典条目或是数据。
- 定义特殊的获取字典条目的方法。比如我想直接获取前三名的奖励配置,只需要调用这个特殊方法就可以取到,不需要取出这三个字典条目,然后分别读奖励配置字段。
- 字典与字典之间可能是有依赖关系的,可能会使用其它字典条目。
- 字典整体的校验、处理。
- 以上步骤会有些部分重合。
FAQ
Q: 字典是放在各个模块中,还是统一存放在字典模块中?
A: 目前倾向放到字典模块中。字典并不涉及到特定业务逻辑,并且一个模块可能需要使用不同功能的字典数据。
Q: 使用哪种数据载体存放字典内容比较合适?
A: 这个还真得根据团队来看,目前在游戏服比较喜欢使用文本格式,查看比较直观,容易追踪版本变化,缺点是可能得写一套专门的导出工具。
Q: 怎么校验字典条目中某些字段的值?
A: 自己手写代码检查属于重复劳动了,可以使用注解(比如javax.validation),统一校验。
代码片段
仅仅用作参考演示。
/**
* 数据字典基类。
*/
public abstract class Dict<T extends Entry> {
/** 存放原始字典数据条目,不要直接使用它。 */
protected TreeMap<Integer, T> primitiveEntries;
/** 生成字典条目实例。 */
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() { ... }
}