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

相关推荐
妙蛙种子3112 小时前
【Java设计模式 | 创建者模式】建造者模式
java·开发语言·后端·设计模式·建造者模式
老实巴交的麻匪2 小时前
Exception异常架构设计:系统性异常处理的思维革命(05)
运维·云原生·架构
爱学习的小囧2 小时前
VMware NSX-T Data Center 3.2.3.0 部署后账号密码获取及登录配置教程
linux·运维·服务器·网络·数据库·esxi
喵了几个咪2 小时前
MySQL 运维实战:ibd 文件批量转换为 SQL 完整指南(基于 ibd2sql)
运维·sql·mysql
2501_946786202 小时前
如何高效查找同时持有CCRC和CMA双认证的信息安全服务商?
服务器·网络·安全
瀚高PG实验室3 小时前
瀚高数据库安全版4.5.10及其以上版本使用pg_cron定时任务
服务器·数据库·瀚高数据库
bukeyiwanshui3 小时前
20260417 NFS服务器
linux·运维·服务器
冲浪中台3 小时前
从追逐技术到回归业务本质,吃互联网红利罢了
服务器·前端·人工智能·低代码
wzl202612133 小时前
企微关键词拉群之批量自动化与风控规避方案
运维·自动化·企业微信