简体   繁体   English

如何使用接口允许超过 1 种结构类型使代码更有用?

[英]How can I use interfaces to allow for more than 1 struct type to make code more useable?

I have a struct that looks like this:我有一个看起来像这样的结构:

type BetaKey struct {
    Id           int64  `json:"-"`
    IssuerId     int64  `json:"-"          db:"issuer_id"`
    SubjectEmail string `json:"-"          db:"subject_email"`
    IssuedTime   int64  `json:"-"          db:"issued_time"`
    ExpiryTime   int64  `json:"expiryTime" db:"expiry_time"`
    Betakey      string `json:"accessKey"`
    Description  string `json:"description"`
}

And within the same package I have a function that returns a slice of BetaKey :在同一个包中,我有一个返回BetaKey切片的BetaKey

func buildResults(query string) ([]BetaKey, error) {
    results := []BetaKey{}
    rows, err := sql.DB.Queryx(query)
    if err != nil {
        return results, err
    }
    defer rows.Close()
    for rows.Next() {
        var bk BetaKey
        err := rows.StructScan(&bk)
        if err != nil {
            return results, err
        }
        results = append(results, bk)
    }
    err = rows.Err()
    if err != nil {
        return results, err
    }
    return results, nil
}

Is it possible for me to rewrite this function so that it takes in a query string but also a type of BetaKey as interface{} , and returns a slice of interface{} so that I can reuse the code instead of copy pasting this into every package because it is literally the same but the only difference is the name of the struct that changes.我是否可以重写此函数,以便它接受查询字符串和BetaKey类型作为interface{} ,并返回interface{}以便我可以重用代码而不是将其复制粘贴到每个包,因为它实际上是相同的,但唯一的区别是更改的结构的名称。

Is this possible?这可能吗? And also is this advised?这也是建议吗? If not, then why?如果不是,那为什么呢?

I write a little example using json, instead of sql rows.我用json而不是sql行写了一个小例子。 You can try to develop from this code :您可以尝试从此代码开发:

package main

import (
    "fmt"
    "reflect"
    "encoding/json"
)

type A struct {
    AField int `json:"a"`
}

type B struct {
    BField string `json:"b"`
}

func build(str string, typ reflect.Type) interface{} {
    results := reflect.MakeSlice(reflect.SliceOf(typ), 0, 10)
    for i:=0; i < 5; i++ {
        res := reflect.New(typ)
        json.Unmarshal([]byte(str), res.Interface())
        results = reflect.Append(results, res.Elem())
    }
    return results.Interface();
}

func main() {
    a := build("{ \"a\": 15 }", reflect.TypeOf(&A{}))
    fmt.Printf("%T : %V\n", a, a)
    b := build("{ \"b\": \"my string\" }", reflect.TypeOf(&B{}))
    fmt.Printf("%T : %V\n", b, b)
}

Golang Playground Golang游乐场

Generics could be used to implement such thing, but Go does not support generics.泛型可以用来实现这样的事情,但 Go 不支持泛型。 To do what you want in Go, you need to use reflection.要在 Go 中做你想做的事,你需要使用反射。

You may change your function to take 1 additional parameter, a reflect.Type for example which designates type of values the individual rows should be loaded into.您可以将函数更改为采用 1 个附加参数,例如, reflect.Type指定应将各个行加载到的值类型。

Then you can use reflect.New() to create a new value of this type and acquire a pointer to it.然后你可以使用reflect.New()来创建一个这个类型的新值并获取一个指向它的指针。 You can use Value.Interface() to obtain the pointer value as a type of interface{} from the reflect.Value value.您可以使用Value.Interface()reflect.Value值中获取指针值作为interface{}类型interface{} This interface{} wrapping the pointer can now be passed to Rows.StructScan() .这个包装指针的interface{}现在可以传递给Rows.StructScan()

And if you want the result slice to hold non-pointer values, you can use reflect.Indirect() to get the pointed value (and another reflect.Interface() to extract the struct value as an interface{} ).如果您希望结果切片保存非指针值,您可以使用reflect.Indirect()来获取指向值(以及另一个reflect.Interface()将结构值提取为interface{} )。

Example code:示例代码:

func buildResults(query string, t reflect.Type) ([]interface{}, error) {
    results := []interface{}{}
    rows, err := sql.DB.Queryx(query)
    if err != nil {
        return results, err
    }
    defer rows.Close()
    for rows.Next() {
        val := reflect.New(t)
        err := rows.StructScan(val.Interface())
        if err != nil {
            return results, err
        }
        i_ := reflect.Indirect(val)
        result = append(result, i_.Interface())
    }
    err = rows.Err()
    if err != nil {
        return results, err
    }
    return results, nil
}

The heart of it is the for block:它的核心是for块:

val := reflect.New(t)                   // A pointer to a new value (of type t)
err := rows.StructScan(val.Interface()) // Pass the pointer to StructScan
if err != nil {
    return results, err
}
i_ := reflect.Indirect(val)             // Dereference the pointer
result = append(result, i_.Interface()) // And add the non-pointer to the result slice

Here's how you can test it:测试方法如下:

type BetaKey struct {
    Id   string
    Name string
}

type AlphaKey struct {
    Id     string
    Source string
}

r, err := buildResults("", reflect.TypeOf(AlphaKey{}))
fmt.Printf("%T %+v %v\n", r[0], r, err)

r, err = buildResults("", reflect.TypeOf(BetaKey{}))
fmt.Printf("%T %+v %v\n", r[0], r, err)

Output:输出:

main.AlphaKey [{Id:aa Source:asource} {Id:aa Source:asource} {Id:aa Source:asource}] <nil>
main.BetaKey [{Id:aa Name:aname} {Id:aa Name:aname} {Id:aa Name:aname}] <nil>

Try it on the Go Playground .Go Playground上试一试。

Notes:笔记:

The above solution will return a value of type of []interface{} whose elements will be of static type interface{} and their dynamic type will be the one specified by the reflect.Type argument.上面的解决方案将返回一个[]interface{}类型的值,其元素将是静态类型interface{}并且它们的动态类型将是由reflect.Type参数指定的reflect.Type So for example if you call it with type:例如,如果您使用以下类型调用它:

bt := reflect.TypeOf(BetaKey{})

The values in the result slice will have dynamic type BetaKey so you can safely type assert them like this:结果切片中的值将具有动态类型BetaKey因此您可以像这样安全地输入 assert它们:

results, err := buildResults("some query", bt)
if err == nil {
    for _, v := range results {
        key := v.(BetaKey)
        // key is of type BetaKey, you may use it like so
    }
} else {
    // handle error
}

To learn more about reflection, read the blog post:要了解有关反射的更多信息,请阅读博客文章:

The Go Blog: The Laws of Reflection Go 博客:反射定律

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM