Go自定义初始化对象

· 1068字 · 3分钟

1. 问题 🔗

不同于Java和C++等语言,Go语言并没有设计函数和方法的重载和默认参数,意在避免这些隐式语法带来的心智负担。然而在编码时我们常常想要设计多个构造函数或者工厂函数来初始化对象,这个时候最简单直接的方法是创建多个不同名的函数,比如context包为创建不同的上下文提供了多个工厂函数:

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc) {
	return WithDeadlineCause(parent, time.Now().Add(timeout), cause)
}

func WithValue(parent Context, key, val any) Context {
	// ...
}

这种方式对于实现者而言要编写多个不同的函数,而对于调用者而言则需要了解甚至记忆好几个函数,那我们有办法解决这个问题吗?

2. 配置结构体 🔗

由于Go没有默认参数,我们可以把需要配置的可选字段抽出来,放在一个导出的结构体里,作为参数传递给函数或方法。例如当我们实现一个Web Server时,监听地址Addr是必选项,而KeepAliveTimeout等为可选项,我们可以把这些可选参数作为配置结构体的成员传递给函数。

type WebServer struct {
	Addr             string
	KeepAliveTimeout int
	MaxConnections   int
}

type NewWebServerOption struct {
	KeepAliveTimeout int
	MaxConnections   int
}

func NewWebServer(addr string, option *NewWebServerOption) {
	// 函数实现需要判断option是不是nil,以及option中各个字段是不是空的
    server := defaultServer()
    if option == nil {
        return
    }
    
    if KeepAliveTimeout > 0 {
        //...
    }
    if maxConnections > 0 {
        //...
    }
}

然而这就会带来一个问题,我们要在函数中判断传递的配置对象或者配置对象的字段是不是空的,需要写比较多的校验,导致代码比较冗余。

3. 函数选项模式 🔗

使用建造者模式可以配置对象带来的错误校验较多的问题,我们可以定义一个Builder结构体,并在其上定义多个用于配置对象的方法和一个返回最后对象的build方法。建造者模式解决了配置结构体的校验问题,不过引入了一个新的Builder结构体。

函数选项模式(Functional Options Pattern)是一种经典的Go编程范式,在很多流行库中都有应用,与建造者模式相比它并不需要定义一个Builder结构体。我们可以利用Go的可选参数特性,向函数传递用于修改待配置对象字段的函数。对于实现者而言,为了便于调用者自定义参数,需要定义高阶函数,这些高阶函数会根据调用者的自定义字段的值返回一个Functional Option,用于配置对象。

type WebServer struct {
	Addr             string
	KeepAliveTimeout int
	MaxConnections   int
}

type NewWebServerOption func(server *WebServer)

func NewWebServer(addr string, options ...NewWebServerOption) {
	server := &WebServer{Addr: addr}

	for _, option := range options {
		option(server)
	}
}

// 返回Functional Option的高阶函数,用于配置服务器keep alive超时时间
func WithKeepAliveTimeout(timeout int) NewWebServerOption {
	return func(server *WebServer) {
		if timeout > 0 {
			server.KeepAliveTimeout = timeout
		}
	}
}

// 返回Functional Option的高阶函数,用于配置服务器最大连接数
func WithMaxConnections(connections int) NewWebServerOption {
	return func(server *WebServer) {
		if connections > 0 {
			server.MaxConnections = connections
		}
	}
}
// 调用者
func main() {
	// ...
    server := NewWebServer(":8080", WithMaxConnections(1000))
    // ...
}

4. 引用 🔗

comments powered by Disqus