关于golang:一文读懂Go匿名结构体使用场景

34次阅读

共计 3670 个字符,预计需要花费 10 分钟才能阅读完成。

前言

匿名行为在 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 数据打交道。

比方 post 申请的 content-type 应用 application/json 时,服务器接管过去的 json 数据是 key:value 格局,不同 key 的 value 的类型能够不一样,可能是数字、字符串、数组等,因而会遇到应用 json.Unmarshalmap[string]interface{}来接管 JSON 反序列化后的数据。

然而应用 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…

正文完
 0