扩展星盘

用结构体嵌入给星盘补自定义分析方法。

斗数的分析规则千人千面,库不可能穷举。Go 不允许给其他包的类型加方法, 因此扩展点是嵌入:把 *Astrolabe 嵌进自己的结构体, 新方法与内置方法一样用点号调用,且是编译期检查的。

配方

定义一个结构体,嵌入 *iztro.Astrolabe
给这个结构体加方法
用排出的星盘构造它
package main

import (
    "strings"

    "github.com/x-haose/x-iztro/go/iztro"
)

// MyChart 嵌入星盘,补自己的分析方法。
type MyChart struct {
    *iztro.Astrolabe
}

// MajorStar 返回命宫主星名(空宫借对宫),多颗以逗号分隔。
func (c MyChart) MajorStar() string {
    soul := c.Palace(iztro.PalaceSoul)
    source := soul
    if soul.IsEmpty() {
        source = soul.OppositePalace()
    }

    names := make([]string, 0, len(source.MajorStars))
    for _, s := range source.MajorStars {
        if s.Type == iztro.StarTypeMajor {
            names = append(names, s.Name)
        }
    }
    return strings.Join(names, ",")
}

// FiveElementsValue 返回五行局的局数。
func (c MyChart) FiveElementsValue() int {
    return map[string]int{
        iztro.ClassWater2nd: 2,
        iztro.ClassWood3rd:  3,
        iztro.ClassMetal4th: 4,
        iztro.ClassEarth5th: 5,
        iztro.ClassFire6th:  6,
    }[c.FiveElementsClassKey]
}

用法

chart, _ := iztro.BySolar("2000-8-16", 2, iztro.GenderFemale, true, iztro.LanguageZhCN, nil)
my := MyChart{chart}

fmt.Println(my.MajorStar())
fmt.Println(my.FiveElementsValue())

// 内置字段与方法照常可用
fmt.Println(my.SolarDate)
fmt.Println(my.Palace(iztro.PalaceSoul).Name)

// 扩展方法随排盘语言输出
enChart, _ := iztro.BySolar("2000-8-16", 2, iztro.GenderFemale, true, iztro.LanguageEnUS, nil)
fmt.Println(MyChart{enChart}.MajorStar())

输出

紫微
3
2000-8-16
命宫
emperor

嵌入而非包装

*iztro.Astrolabe 而不是 Astrolabe *iztro.Astrolabe—— 前者让内置字段与方法直接提升到外层,my.SolarDate 就能取到; 后者每次都得写 my.Astrolabe.SolarDate


扩展别的类型

同一套写法适用于宫位与星耀:

type MyPalace struct {
    *iztro.Palace
}

// IsAfflicted 判断本宫是否「煞忌交冲」:坐煞星且带化忌。
func (p MyPalace) IsAfflicted() bool {
    return p.HasOneOf(
        iztro.StarQingyangMin, iztro.StarTuoluoMin,
        iztro.StarHuoxingMin, iztro.StarLingxingMin,
        iztro.StarDikongMin, iztro.StarDijieMin,
    ) && p.HasMutagen(iztro.MutagenJi)
}
for i := range chart.Palaces {
    p := MyPalace{&chart.Palaces[i]}
    if p.IsAfflicted() {
        fmt.Println(p.Name, "煞忌交冲")
    }
}

输出

疾厄 煞忌交冲

用接口约束扩展

要求多种星盘类型提供同一组分析能力时,用接口:

type WealthAnalyzer interface {
    WealthScore() int
    HasWealthPattern() bool
}

func report(a WealthAnalyzer) {
    fmt.Println(a.WealthScore(), a.HasWealthPattern())
}

任何实现了这两个方法的类型都能传进去,编译期检查。


组织建议


与运行期注入的区别

嵌入在编译期完成,与运行期往对象上挂函数的做法相比:

嵌入运行期注入
方法是否存在编译期确定运行期才知道
类型检查
调用开销与内置方法相同多一次动态查找
出错时机编译失败运行时报错
作用范围只影响自己的类型全局或按实例

代价是扩展方法必须在编译期就写好,不能由配置文件或用户输入动态决定。 需要那种灵活度时,用一张 map[string]func(*iztro.Astrolabe) bool 自行分派。

本页目录