扩展星盘
用扩展 trait 给 Astrolabe、PalaceRef 补自定义分析方法。
斗数的分析规则千人千面,库不可能穷举。x-iztro 的做法是让你把自己的规则 以方法的形式挂到星盘上——调用语法与内置方法一致,且在编译期完成,类型受检、零运行期开销。
配方
定义一个 trait,声明你要补的方法
为
Astrolabe(或 PalaceRef、StarRef)实现它使用处
use 这个 trait,方法即可用use x_iztro::i18n::translate_star;
use x_iztro::*;
/// 给星盘补两个自定义分析方法。
trait MyAnalysis {
/// 命宫主星名(空宫借对宫),多颗以逗号分隔
fn major_star(&self) -> String;
/// 五行局的局数
fn five_elements_value(&self) -> usize;
}
impl MyAnalysis for Astrolabe {
fn major_star(&self) -> String {
let soul = self.palace(Palace::Soul).expect("命宫必然存在");
let source = if soul.is_empty() { soul.opposite_palace() } else { soul };
source
.major_stars
.iter()
.filter(|s| s.star_type == StarType::Major)
.map(|s| translate_star(s.key, self.language))
.collect::<Vec<_>>()
.join(",")
}
fn five_elements_value(&self) -> usize {
self.five_elements_class.value()
}
}用法
let chart = by_solar("2000-8-16", 2, Gender::Female, true, Language::ZhCN, Config::default())?;
println!("{}", chart.major_star());
println!("{}", chart.five_elements_value());
// 扩展方法随排盘语言输出
let en = by_solar("2000-8-16", 2, Gender::Female, true, Language::EnUS, Config::default())?;
println!("{}", en.major_star());输出
紫微
3
emperor扩展别的类型
同一套写法适用于宫位与星耀。为视图类型实现时注意生命周期参数:
trait PalaceAnalysis {
/// 本宫是否「煞忌交冲」:坐煞星且带化忌
fn is_afflicted(&self) -> bool;
}
impl PalaceAnalysis for PalaceRef<'_> {
fn is_afflicted(&self) -> bool {
use x_iztro::StarKey::*;
self.has_one_of(&[QingyangMin, TuoluoMin, HuoxingMin, LingxingMin, DikongMin, DijieMin])
&& self.has_mutagen(Mutagen::Ji)
}
}for palace in &chart.palaces {
let p = chart.palace(palace.index).unwrap();
if p.is_afflicted() {
println!("{} 煞忌交冲", translate_palace(p.name, Language::ZhCN));
}
}输出
疾厄 煞忌交冲组织建议
与运行期注入的区别
有些库让你在运行期把函数挂到对象上。Rust 的扩展 trait 在编译期完成同一件事, 差别在于:
| 扩展 trait | 运行期注入 | |
|---|---|---|
| 方法是否存在 | 编译期确定 | 运行期才知道 |
| 类型检查 | 有 | 无 |
| 调用开销 | 与内置方法相同 | 多一次动态查找 |
| 出错时机 | 编译失败 | 运行时报错 |
| 作用范围 | use 了才可见 | 全局或按实例 |
代价是扩展方法必须在编译期就写好,不能由配置文件或用户输入动态决定。
需要那种灵活度时,用一张 HashMap<String, fn(&Astrolabe) -> bool> 自行分派。