让编程更优雅-Go语言函数选型模式
大家好,我是gopher_looklook,现任某独角兽企业Go语言工程师,喜欢钻研Go源码,发掘各项技术在大型Go微服务项目中的最佳实践,期待与各位小伙伴多多交流,共同进步!
为什么需要函数选项模式
介绍 函数选项模式(functional option pattern) 之前,我们先来看这么一段代码。
type Server struct { host string port int } func NewServer(host string, port int) *Server { s := &Server{ host: host, port: port, } return s } func main() { server := NewServer("127.0.0.1", 6379) // 打印配置 fmt.Printf("Server: %+v\n", *server) }
这段代码很简单。首先是定义了一个Server结构体配置,并定义了Server的成员变量host和port。之后提供了一个构造Server类型指针的构造函数 NewServer(host string, port int),需要传入参数host和port,用于初始化Server成员变量的具体值。从程序逻辑来讲,这段代码没有任何问题。但是如果考虑到扩展性,构造函数 NewServer 其实是非常脆弱的。
假设随着业务或架构的演进,Server结构体配置的参数逐渐增多,比如新增了两个字段timeout和maxConn,用于指定超时时间和最大连接数,这时候就需要修改构造函数的代码如下:
可以发现随着结构体成员变量的逐渐增多,需要不断修改构造函数的入参个数,并且原先进行初始化函数的代码如果不进行对应的修改,由于函数入参个数的不匹配,原先正常运行的代码会报如下错误:
Not enough arguments in call to 'NewServer'。这显然很不符合软件工程的开闭原则,严重影响代码的稳定性和可维护性。 那么有没有办法规避这种问题呢?这时候,函数选项模式就派上用场了。
函数选项模式的使用
函数选项模式(functional option pattern)是一种设计模式,用于在Go语言中灵活配置函数参数,特别适用于函数参数较多或函数参数可选的场景,可以避免构造函数列表过长和频繁变动的问题。函数选项模式通过使用函数作为参数,允许调用者通过更加灵活的方式设置默认选项。
利用函数选项模式,我们可以修改上述代码如下
在修改后的代码里,除了初始的host和port字段,之后Server结构体新增的字段,都可以通过构造可选参数进行传递。如果不传,则默认使用构造函数提供的初始值。对于构造函数的提供方,每新增一个字段,仅需要在构造函数指定新字段的默认初始值和提供对应的配置函数方法。配置函数的写法也比较固定,通常以With开头,返回一个给指定结构体指定成员变量赋值的函数:
type StructOption func(*Struct) // Struct: 随时可能增添新成员参数的结构体,例如上述的Server func WithXxx(Xxx Type) StructOption{ // Type: Xxx自身的变量类型 return func(s *Struct){ s.Xxx = Xxx } }
函数选项模式,既保证了旧代码的正常编译与运行,同时也便于调用方更加灵活地初始化结构体。
开源项目使用函数选项模式
Go语言的微服务框架go-micro源码就使用到了函数选项模式
飞书项目开放接口SDK project-oapi-sdk-golang在配置API Client时,也使用到了函数选项模式。