Builder模式在BUFR数据组装中的实践

Builder模式在BUFR数据组装中的实践

一、背景分析

在气象业务系统中,BUFR 报文的生成往往涉及多个复杂对象的组装:站点信息(StationInfo)、观测数据(如 AwsHourDataRadiationHourData)、编码选项(EncodeOptions)等。如果直接通过构造函数或逐字段赋值的方式创建这些对象,会导致调用端代码冗长、可读性差,且容易出现部分字段未初始化的空指针问题。

bufrv2builder.go 中引入了 Builder 模式 ,为 StationInfoAwsHourDataAwsMinuteData 和最终的 BUFR 报文对象提供了链式调用的构建 API。本文将详细分析其设计动机、源码实现以及在气象数据组装场景下的应用价值。

二、Builder 模式的设计动机

2.1 直接构造的问题

假设没有 Builder,调用端需要编写如下代码:

go 复制代码
station := &StationInfo{
    StationID:   "54511",
    StationName: "北京",
    Latitudes:   "3956  ",
    Longitudes:  "11617 ",
    Elevation:   31.3,
    ObsType:     &ObserveType{},
}

data := &AwsHourData{
    ObservationTime: time.Date(2024, 1, 15, 12, 0, 0, 0, time.UTC),
    Pressure: &PressureHourInfo{
        StationPress: &BufrField{Value: "1013.2", QC: "0"},
    },
    TempHumidity: &TempHumidityHourInfo{
        AirTemp: &BufrField{Value: "25.3", QC: "0"},
    },
    // ... 继续嵌套初始化
}

这种方式存在明显缺陷:

  • 嵌套结构体初始化层级深,容易遗漏中间层指针(如 Pressurenil)。
  • BufrField 的格式化逻辑散落在调用端,重复且易错。
  • 参数顺序无自解释性,可维护性差。

2.2 Builder 模式的优势

通过 Builder,上述代码可以简化为:

go 复制代码
station := NewStationInfoBuilder().
    SetStationID("54511").
    SetStationName("北京").
    SetLatitudes("3956  ").
    SetLongitudes("11617 ").
    SetElevation(31.3).
    Build()

data := NewAwsHourDataBuilder().
    SetObservationTime(time.Date(2024, 1, 15, 12, 0, 0, 0, time.UTC)).
    SetStationPress(1013.2, "0").
    SetAirTemp(25.3, "0").
    Build()

三、BufrBuilder:顶层报文构建器

BufrBuilder 是生成最终 []byte BUFR 报文的入口:

go 复制代码
// BufrBuilder BUFR 构建器
type BufrBuilder struct {
	encoder *BaseEncoder
	data    BufrData
	station *StationInfo
	opts    *EncodeOptions
}

func NewBufrBuilder() *BufrBuilder {
	return &BufrBuilder{
		encoder: NewEncoder().(*BaseEncoder),
		opts:    DefaultEncodeOptions,
	}
}

3.1 链式 API

go 复制代码
func (b *BufrBuilder) SetVersion(version BufrVersion) *BufrBuilder {
	b.opts.Version = version
	return b
}

func (b *BufrBuilder) SetUseBitmap(use bool) *BufrBuilder {
	b.opts.UseBitmap = use
	return b
}

func (b *BufrBuilder) SetStation(station *StationInfo) *BufrBuilder {
	b.station = station
	return b
}

func (b *BufrBuilder) SetAwsHourData(data *AwsHourData) *BufrBuilder {
	b.data = data
	return b
}

func (b *BufrBuilder) SetAwsMinuteData(data *AwsMinuteData) *BufrBuilder {
	b.data = data
	return b
}

func (b *BufrBuilder) SetRadiationHourData(data *RadiationHourData) *BufrBuilder {
	b.data = data
	return b
}

3.2 Build 方法

go 复制代码
func (b *BufrBuilder) Build() ([]byte, error) {
	if b.data == nil {
		return nil, ErrInvalidBufrData
	}
	if b.station == nil {
		return nil, ErrInvalidBufrData
	}
	return b.encoder.Encode(b.data, b.station, b.opts)
}

Build() 在最终阶段执行校验,确保数据和站点信息都已设置,然后委托给 BaseEncoder.Encode 完成二进制编码。

四、数据对象 Builder

4.1 AwsHourDataBuilder

go 复制代码
type AwsHourDataBuilder struct {
	data *AwsHourData
}

func NewAwsHourDataBuilder() *AwsHourDataBuilder {
	return &AwsHourDataBuilder{
		data: &AwsHourData{
			ObservationTime: time.Now().UTC(),
			Pressure:        &PressureHourInfo{},
			TempHumidity:    &TempHumidityHourInfo{},
			Rain:            &RainHourInfo{},
			Wind:            &WindHourInfo{},
			Weather:         &WeatherInfo{},
		},
	}
}

设计亮点NewAwsHourDataBuilder 在构造函数中就预初始化了所有嵌套指针,彻底杜绝了调用端因忘记初始化 PressureWind 而导致的空指针异常。

4.2 字段设置方法

go 复制代码
func (b *AwsHourDataBuilder) SetObservationTime(t time.Time) *AwsHourDataBuilder {
	b.data.ObservationTime = t
	return b
}

func (b *AwsHourDataBuilder) SetStationPress(value float64, qc string) *AwsHourDataBuilder {
	b.data.Pressure.StationPress = &BufrField{Value: FormatFloat(value, 1), QC: qc}
	return b
}

func (b *AwsHourDataBuilder) SetSeaLevelPress(value float64, qc string) *AwsHourDataBuilder {
	b.data.Pressure.SeaLevelPress = &BufrField{Value: FormatFloat(value, 1), QC: qc}
	return b
}

func (b *AwsHourDataBuilder) SetAirTemp(value float64, qc string) *AwsHourDataBuilder {
	b.data.TempHumidity.AirTemp = &BufrField{Value: FormatFloat(value, 1), QC: qc}
	return b
}

func (b *AwsHourDataBuilder) SetRelHumidity(value float64, qc string) *AwsHourDataBuilder {
	b.data.TempHumidity.RelHumidity = &BufrField{Value: FormatFloat(value, 0), QC: qc}
	return b
}

func (b *AwsHourDataBuilder) SetRain1H(value float64, qc string) *AwsHourDataBuilder {
	b.data.Rain.Rain1H = &BufrField{Value: FormatFloat(value, 1), QC: qc}
	return b
}

func (b *AwsHourDataBuilder) SetWind(dir2m, speed2m, dir10m, speed10m float64, qc string) *AwsHourDataBuilder {
	b.data.Wind.WindDir2Ms = &BufrField{Value: FormatFloat(dir2m, 0), QC: qc}
	b.data.Wind.WindSpeed2Ms = &BufrField{Value: FormatFloat(speed2m, 1), QC: qc}
	b.data.Wind.WindDir10Ms = &BufrField{Value: FormatFloat(dir10m, 0), QC: qc}
	b.data.Wind.WindSpeed10Ms = &BufrField{Value: FormatFloat(speed10m, 1), QC: qc}
	return b
}

func (b *AwsHourDataBuilder) SetCurrentWeather(code int, qc string) *AwsHourDataBuilder {
	b.data.Weather.CurrentWeather = &BufrField{Value: FormatFloat(float64(code), 0), QC: qc}
	return b
}

关注点分离

  • 调用端只需传入原始浮点数和质控码。
  • FormatFloat 的调用被封装在 Builder 内部,保证了字符串格式化的一致性。
  • 不同要素使用不同的小数精度(气压 1 位、气温 1 位、相对湿度 0 位),这些业务规则被收敛到 Builder 中。

4.3 AwsMinuteDataBuilder

分钟数据的 Builder 与小时数据类似,但字段集合更精简:

go 复制代码
type AwsMinuteDataBuilder struct {
	data *AwsMinuteData
}

func NewAwsMinuteDataBuilder() *AwsMinuteDataBuilder {
	return &AwsMinuteDataBuilder{
		data: &AwsMinuteData{
			ObservationTime: time.Now().UTC(),
			Pressure:        &PressureInfo{},
			TempHumidity:    &TempHumidityInfo{},
			Rain:            &RainMinuteInfo{},
			Wind:            &WindMinuteInfo{},
		},
	}
}

分钟数据特有的 SetMinuteRain 和简化版 SetWind

go 复制代码
func (b *AwsMinuteDataBuilder) SetMinuteRain(value float64, qc string) *AwsMinuteDataBuilder {
	b.data.Rain.MinuteRain = &BufrField{Value: FormatFloat(value, 1), QC: qc}
	return b
}

func (b *AwsMinuteDataBuilder) SetWind(dir2m, speed2m float64, qc string) *AwsMinuteDataBuilder {
	b.data.Wind.WindDir2Ms = &BufrField{Value: FormatFloat(dir2m, 0), QC: qc}
	b.data.Wind.WindSpeed2Ms = &BufrField{Value: FormatFloat(speed2m, 1), QC: qc}
	return b
}

五、StationInfoBuilder

站点信息同样需要 Builder 来简化嵌套结构的初始化:

go 复制代码
type StationInfoBuilder struct {
	station *StationInfo
}

func NewStationInfoBuilder() *StationInfoBuilder {
	return &StationInfoBuilder{
		station: &StationInfo{
			ObsType: &ObserveType{},
		},
	}
}

func (b *StationInfoBuilder) SetStationID(id string) *StationInfoBuilder {
	b.station.StationID = id
	return b
}

func (b *StationInfoBuilder) SetStationName(name string) *StationInfoBuilder {
	b.station.StationName = name
	return b
}

func (b *StationInfoBuilder) SetLatitudes(lat string) *StationInfoBuilder {
	b.station.Latitudes = lat
	return b
}

func (b *StationInfoBuilder) SetLongitudes(lon string) *StationInfoBuilder {
	b.station.Longitudes = lon
	return b
}

func (b *StationInfoBuilder) SetElevation(elev float64) *StationInfoBuilder {
	b.station.Elevation = elev
	return b
}

func (b *StationInfoBuilder) SetPessureElevation(elev float64) *StationInfoBuilder {
	b.station.PessureElevation = elev
	return b
}

func (b *StationInfoBuilder) SetStationCharCode(code string) *StationInfoBuilder {
	b.station.StationCharCode = code
	return b
}

func (b *StationInfoBuilder) SetWindSpeHeight(height float64) *StationInfoBuilder {
	b.station.WindSpeHeight = height
	return b
}

func (b *StationInfoBuilder) Build() *StationInfo {
	return b.station
}

六、Builder 模式架构图

复制代码
+-----------------------------------------------------------+
|                        调用端                              |
+-----------------------------------------------------------+
                            |
        +-------------------+-------------------+
        v                   v                   v
+---------------+   +---------------+   +---------------+
| StationInfo   |   | AwsHourData   |   |  BufrBuilder  |
|    Builder    |   |    Builder    |   |               |
+-------+-------+   +-------+-------+   +-------+-------+
        |                   |                   |
        | SetStationID()    | SetStationPress() | SetVersion()
        | SetLatitudes()    | SetAirTemp()      | SetStation()
        | SetElevation()    | SetWind()         | SetAwsHourData()
        v                   v                   v
+-------+-------+   +-------+-------+   +-------+-------+
|  StationInfo  |   |  AwsHourData  |   |  EncodeOptions |
+-------+-------+   +-------+-------+   +-------+-------+
        |                   |                   |
        +-------------------+-------------------+
                            |
                            v
                   +--------+--------+
                   |  BufrBuilder    |
                   |     Build()     |
                   +--------+--------+
                            |
                            v
                   +--------+--------+
                   | BaseEncoder     |
                   |   Encode()      |
                   +--------+--------+
                            |
                            v
                   +--------+--------+
                   |   []byte 报文   |
                   +-----------------+

七、Builder 模式的优势总结

优势 说明
链式调用 通过返回 *Builder 自身,支持流畅的链式 API,代码可读性高。
安全初始化 构造函数中预初始化所有嵌套指针,避免空指针异常。
格式化收敛 FormatFloat 等工具函数的调用被封装在 Builder 内部,保证一致性。
可选参数友好 调用端只需设置关心的字段,未设置的字段保持默认值或缺测状态。
编译时类型安全 每个 Builder 都有明确的类型约束,避免了弱类型 map 或反射带来的风险。

八、总结

bufrv2 通过 Builder 模式有效地解决了气象数据对象构造复杂、嵌套层级深、格式化逻辑分散的问题。BufrBuilder 作为顶层编排器,AwsHourDataBuilder / AwsMinuteDataBuilder 作为数据对象构建器,StationInfoBuilder 作为元信息构建器,三者协同工作,为调用端提供了一套类型安全、语义清晰、易于扩展的 API。

https://github.com/0voice

相关推荐
大树881 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
小宇宙Zz2 天前
Maven依赖冲突
java·服务器·maven
Inhand陈工2 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智2 天前
ARP代理--工作原理
运维·网络·arp·arp代理
shushangyun_2 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
古城小栈2 天前
Unix 与 Linux 异同小叙
linux·服务器·unix
施努卡机器视觉2 天前
SNK施努卡侧滑门锁上滑轮总成自动化装配线,从零件到组件,全流程精密制造方案
运维·自动化·制造
程序猿阿伟2 天前
《Chrome离线扩展安装的底层逻辑与场景落地指南》
服务器·网络·chrome