变长参数函数的妙用
我们使用得最多是因为最常用的 fmt 包、log 包中的几个导出函数都是变长参数函数。
Go 内置的常用语切片类型操作的 append 函数也是变长参数函数。
func append(slice []Type, elems ...Type) []Type
什么是变长参数函数
顾名思义,变长参数函数就是指调用时可以接受零个,一个或多个实际参数的函数。如:
println("hello")
println()
println("hello", "world")
我们看到,无论传入多少个参数,println 都可以正常工作。
一个变长参数函数的声明形式如下:
func funcName(arg ...Type) {
// ...
}
另外,一个变长参数函数只能有一个 ...Type
类型的参数,并且必须是最后一个参数。
变长参数函数的 ...Type
类型形式参数再函数体内为 []Type
的变量,我们可以理解为一个语法糖。
func sum(agrs ...int) int {
var sum int
for _, arg := range args {
sum += arg
}
return sum
}
在函数外部,...Type
形式参数可匹配和接受的实参类型有两种:
- 多个 Type 类型的变量
t...
(t 为 T 类型变量)
func main() {
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum([]int{1, 2, 3}...)) // 6
}
我们只能选择上述的两种实参类型的其中一种,不能同时使用。
另外。使用变长参数函数,最容易出现一个问题是实参与形参不匹配,如下:
func dump(args ...interface{}) {
for _, arg := range args {
fmt.Println(arg)
}
}
func main() {
a := []string{"a", "b", "c"}
dump(a) // panic: cannot use a (type []string) as type []interface {} in argument to dump
}
这是因为 a
是 []string
类型,而 dump
函数的形参是 ...interface{}
类型,这两者不匹配。
虽然 string
类型是 interface 类型的子集,但是 []string
类型并不是 []interface{}
类型的子集。
也有个例外,如append
。
模拟函数重载
Go 语言不允许在同一个作用域下定义名字相同但函数原型不同的函数。Go 语言官方给出的不支持的理由如下:
其他语言的经验告诉我们,使用具有相同名称但函数签名不同的的多种方法有时会很有用,但在实践中也可能会造成混淆和脆弱性。在 Go 的类型系统中,仅按名称进行匹配并要求类型一致是一个主要的简化策略。
既然不支持,那么我们可以使用变长参数函数来模拟函数重载。
func concat(sep string, args ...interface{}) string {
var result string
for i, v := range args {
if i != 0 {
result += sep
}
switch v.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
result += fmt.Sprintf("%d", v)
case string:
result += fmt.Sprintf("%s", v)
case []int:
ints := v.([]int)
for i, v := range ints {
if i != 0 {
result += sep
}
result += fmt.Sprintf("%d", v)
}
case []string:
strs := v.([]string)
result += strings.Join(strs, sep)
default:
fmt.Println("unsupported type")
return ""
}
}
return result
}
func main() {
println(concat(",", 1, 2, 3)) // 1,2,3
println(concat(",", "a", "b", "c")) // a,b,c
println(concat(",", []int{1, 2, 3})) // 1,2,3
println(concat(",", []string{"a", "b", "c"})) // a,b,c
println(concat(",", 1, "a", []int{2, 3}, []string{"b", "c"})) // 1,a,2,3,b,c
}
实现功能选项模式
我们来看使用选型模式实现的例子:
type Server struct {
addr string
port int
timeout time.Duration
maxConn int
maxReqLen int
readTimeout time.Duration
writeTimeout time.Duration
}
type Option func(*Server)
func Addr(addr string) Option {
return func(s *Server) {
s.addr = addr
}
}
func Port(port int) Option {
return func(s *Server) {
s.port = port
}
}
func Timeout(timeout time.Duration) Option {
return func(s *Server) {
s.timeout = timeout
}
}
func MaxConn(maxConn int) Option {
return func(s *Server) {
s.maxConn = maxConn
}
}
func MaxReqLen(maxReqLen int) Option {
return func(s *Server) {
s.maxReqLen = maxReqLen
}
}
func ReadTimeout(readTimeout time.Duration) Option {
return func(s *Server) {
s.readTimeout = readTimeout
}
}
func WriteTimeout(writeTimeout time.Duration) Option {
return func(s *Server) {
s.writeTimeout = writeTimeout
}
}
func NewServer(addr string, port int, options ...Option) (*Server, error) {
srv := &Server{
addr: addr,
port: port,
timeout: 30 * time.Second,
maxConn: 1024,
maxReqLen: 1024,
readTimeout: 5 * time.Second,
writeTimeout: 5 * time.Second,
}
for _, option := range options {
option(srv)
}
return srv, nil
}
func main() {
srv, err := NewServer("localhost", 8080, Timeout(60 * time.Second), MaxConn(2048))
if err != nil {
fmt.Println(err)
return
}
fmt.Println(srv) // &{localhost 8080 60000000000 2048 1024 5000000000 5000000000}
}
功能选项模式是一种用于构建复杂对象的创建模式。它可以用于解决构造函数参数过多的问题,也可以用于解决构造函数参数顺序不确定的问题。
可以收获如下好处:
- 更漂亮的、不随时间变化的公共 API;
- 参数可读性更好;
- 配置选项高度可扩展;
- 提供使用默认选项更简单方式;
- 使用更安全;