第 3 章:片段继承
第 3 章:片段继承
真实项目里,很多配置块"长得差不多"——比如多个服务都要 region、timeout、dns。重复写既啰嗦又容易不一致。SML 用**片段(fragment)**解决这个问题。
一句话:片段是"值的复用"——把一组字段定义一次,到处引用展开。
3.1 定义片段:@name
用 @名字 { } 定义一段可复用的字段:
@net {
region: cn-north-1
dns: internal.swebase.cn
timeout: 30
}
注意 @net 本身不会出现在解析结果里,它只是个"模板"。
3.2 引用片段:&name
用 &名字 把片段"展开"进来:
network: &net
解析后 network 会得到:
network {
region: cn-north-1
dns: internal.swebase.cn
timeout: 30
}
3.3 在块内引用
片段常用来给多个服务注入公共字段:
@base {
region: cn-north-1
timeout: 30
}
service auth { &base port: 7100 name: auth-svc }
service billing { &base port: 7200 name: billing-svc }
service auth 展开后等价于:
service auth {
region: cn-north-1
timeout: 30
port: 7100
name: auth-svc
}
service billing 同理拿到同样的 region / timeout,但 port / name 各自不同。复用 + 个性,完美。
3.4 重要细节:块内裸写 &name 不会展开
这是新手最容易踩的坑:
server {
&base # ❌ 这样写,&base 被当成"键名",不会展开字段
port: 8080
}
正确写法是把片段作为值赋给某个键:
server {
net: &base # ✅ net 这个键获得 base 的全部字段
port: 8080
}
或者用 &base 本身作为值的来源(如 3.3 的 service auth { &base ... } 那种"块开头直接跟 &base 再跟额外字段"的写法,解析器会把它当作"先展开片段再合并后续字段")。牢记:片段是"值",要出现在 键: 值 的值位置。
3.5 片段 vs 契约(先预告)
你可能会想:“片段和契约(第 5 章)听起来像?” 它们的定位完全不同:
片段 @base / &base |
契约 @contract / @is |
|
|---|---|---|
| 本质 | 值的复用 | 形状的约束 |
| 做什么 | 把一组字段"展开进来" | 校验结构、补默认值 |
| 结果 | 数据被复制填充 | 数据被检查 + 补齐 |
两者正交,可以一起用(先把片段展开,再用契约校验)。
3.6 动手试一试
为一个游戏服务器集群写配置,三个服共享 region 和 max_players:
@common {
region: ap-east-1
max_players: 64
}
lobby { &common port: 25565 name: 大厅 }
pvp { &common port: 25566 name: 竞技场 }
survival { &common port: 25567 name: 生存 }
动手练习
读完本章,在下面的编辑器里直接修改 SML 并点“运行”,立刻看到解析结果或校验错误——有输出才能高效学习。
✍ 动手练习
定义 @base { region, timeout },再写两个 service 块(auth、billing)都 &base 并各自覆盖 port 与 name。
💡 提示:把 &base 误写成裸词(去掉 &)看看会发生什么。
Q1. 定义可复用片段用哪个符号?
@base { } 定义片段模板,&base 在别处引用它进行继承合并。
Q2. service auth { &base port: 7100 } 里,如果 @base 已定义 port: 7000,结果 port 是?
引用 &base 后,本地字段会覆盖片段中的同名字段,port 取 7100。
Q3. 判断:&base 必须放在块的第一行才能生效。
&base 引用可以出现在块内任意字段位置,SML 会先做片段合并再解析其他字段。
Q4. 片段继承主要解决什么问题?
片段用于把 region/timeout 等公共字段抽出来复用,避免每个服务重复写。