前言
匿名行为在go语言里十分常见,比方:
- 匿名函数:也就是咱们熟知的闭包(Closure)
- 构造体里的匿名字段(Anonymous Fields)
- 匿名构造体(Anonymous Structs)
匿名行为的设计带来了一些了解上的艰难,然而相熟了匿名设计的应用后,你会发现匿名设计在某些特定场景能够帮忙大家写出更简洁、更优雅、更高效和更平安的代码。
什么是匿名构造体
匿名构造体:顾名思义,就是构造体没有命名。比方上面的代码示例:
// example1.go
package main
import (
"fmt"
)
func main() {
a := struct{name string; age int}{"bob", 10}
b := struct{
school string
city string
}{"THU", "Beijing"}
fmt.Println(a, b)
}
在这个例子里,咱们定义了2个变量a和b,它们都是匿名构造体变量。
常见的应用场景
全局变量组合
有时候咱们会在程序里定义若干全局变量,有些全局变量的含意是相互关联的,这个时候咱们能够应用匿名构造体把这些关联的全局变量组合在一起。
// example2.go
package main
import "fmt"
// DBConfig 申明全局匿名构造体变量
var DBConfig struct {
user string
pwd string
host string
port int
db string
}
// SysConfig 全局匿名构造体变量也能够在申明的时候间接初始化赋值
var SysConfig = struct{
sysName string
mode string
}{"tutorial", "debug"}
func main() {
// 给匿名构造体变量DBConfig赋值
DBConfig.user = "root"
DBConfig.pwd = "root"
DBConfig.host = "127.0.0.1"
DBConfig.port = 3306
DBConfig.db = "test_db"
fmt.Println(DBConfig)
}
对全局匿名构造体变量实现赋值后,后续代码都能够应用这个匿名构造体变量。
留神:如果你的程序对于某个全局的构造体要创立多个变量,就不能用匿名构造体了。
局部变量组合
全局变量能够组合,局部变量当然也能够组合了。
如果在部分作用域(比方函数或者办法体内)里,某些变量的含意相互关联,就能够组合到一个构造体里。
同时这个构造体只是长期一次性应用,不须要创立这个构造体的多个变量,那就能够应用匿名构造体。
// example3.go
package main
import "fmt"
func main() {
// a和b作为部分匿名构造体变量,只是长期一次性应用
// 留神:a是把struct里的字段申明放在同一行,字段之间要用分号宰割,否则编译报错
a := struct{name string; age int}{"Alice", 16}
fmt.Println(a)
b := struct{
school string
city string
}{"THU", "Beijing"}
fmt.Println(b)
}
构建测试数据
匿名构造体能够和切片联合起来应用,通常用于创立一组测试数据。
// example4.go
package main
import "fmt"
// 测试数据
var indexRuneTests = []struct {
s string
rune rune
out int
}{
{"a A x", 'A', 2},
{"some_text=some_value", '=', 9},
{"☺a", 'a', 3},
{"a☻☺b", '☺', 4},
}
func main() {
fmt.Println(indexRuneTests)
}
嵌套锁(embed lock)
咱们常常遇到多个goroutine要操作共享变量,为了并发平安,须要对共享变量的读写加锁。
这个时候通常须要定义一个和共享变量配套的锁来爱护共享变量。
匿名构造体和匿名字段相结合,能够写出更优雅的代码来爱护匿名构造体里的共享变量,实现并发平安。
// example5.go
package main
import (
"fmt"
"sync"
)
// hits 匿名构造体变量
// 这里同时用到了匿名构造体和匿名字段, sync.Mutex是匿名字段
// 因为匿名构造体嵌套了sync.Mutex,所以就有了sync.Mutex的Lock和Unlock办法
var hits struct {
sync.Mutex
n int
}
func main() {
var wg sync.WaitGroup
N := 100
// 启动100个goroutine对匿名构造体的成员n同时做读写操作
wg.Add(N)
for i:=0; i<100; i++ {
go func() {
defer wg.Done()
hits.Lock()
defer hits.Unlock()
hits.n++
}()
}
wg.Wait()
fmt.Println(hits.n) // 100
}
HTTP处理函数中JSON序列化和反序列化
咱们在解决http申请时,通常会和JSON数据打交道。
json.Unmarshalmap[string]interface{}
然而应用map[string]interface{}有几个问题:
- 没有类型查看:比方json的某个value原本预期是string类型,然而申请传过来的是bool类型,应用json.Unmarshal解析到map[string]interface{}是不会报错的,因为空interface能够承受任何类型数据。
- map是含糊的:Unmarshal后失去了map,咱们还得判断这个key在map里是否存在。否则拿不存在的key的value,失去的可能是给nil值,如果不做查看,间接对nil指针做*操作,会引发panic。
- 代码比拟简短:得先判断key是否存在,如果存在,要显示转换成对应的数据类型,并且还得判断转换是否胜利。代码会比拟简短。
这个时候咱们就能够应用匿名构造体来接管反序列化后的数据,代码会更简洁。参见如下代码示例:
// example6.go
// 申请命令:curl -X POST -H "content-type: application/json" http://localhost:4000/user -d '{"name":"John", "age":111}'
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
func AddUser(w http.ResponseWriter, r *http.Request) {
// data 匿名构造体变量,用来接管http申请发送过去的json数据
data := struct{
Name string `json:"name"`
Age int `json:"age"`
}{}
// 把json数据反序列化到data变量里
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&data)
if err != nil {
fmt.Println("error:", err)
}
fmt.Println(data)
fmt.Fprint(w, "Hello!")
}
func index(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "index")
}
func main() {
http.HandleFunc("/", index)
http.HandleFunc("/user", AddUser)
log.Fatal(http.ListenAndServe("localhost:4000", nil))
}
总结
匿名构造体能够让咱们不必先定义构造体类型,再定义构造体变量。让构造体的定义和变量的定义能够联合在一起,一次性实现。
匿名构造体有以下利用场景:
-
组合变量:
- 全局匿名构造体:把有关联的全局变量组合在一起
- 部分匿名构造体:长期一次性应用
-
构建测试数据:
- 匿名构造体+切片,结构一组测试数据
- 嵌套锁:把多个goroutine的共享拜访数据和爱护共享数据的锁组合在一个匿名构造体内,代码更优雅。
-
HTTP处理函数中Json序列化和反序列化:
- 匿名构造体变量用来接管http申请数据
- 和map[string]interface{}相比,代码更简洁,更平安
开源地址
文档和代码开源地址:https://github.com/jincheng9/…
也欢送大家关注公众号:coding进阶,学习更多Go、微服务和云原生架构相干常识。
References
- https://go.dev/talks/2012/10t…
- https://qvault.io/golang/anon…