Builder模式在BUFR数据组装中的实践
一、背景分析
在气象业务系统中,BUFR 报文的生成往往涉及多个复杂对象的组装:站点信息(StationInfo)、观测数据(如 AwsHourData、RadiationHourData)、编码选项(EncodeOptions)等。如果直接通过构造函数或逐字段赋值的方式创建这些对象,会导致调用端代码冗长、可读性差,且容易出现部分字段未初始化的空指针问题。
bufrv2 在 builder.go 中引入了 Builder 模式 ,为 StationInfo、AwsHourData、AwsMinuteData 和最终的 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"},
},
// ... 继续嵌套初始化
}
这种方式存在明显缺陷:
- 嵌套结构体初始化层级深,容易遗漏中间层指针(如
Pressure为nil)。 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 在构造函数中就预初始化了所有嵌套指针,彻底杜绝了调用端因忘记初始化 Pressure 或 Wind 而导致的空指针异常。
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。