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

时间:2016-05-09 06:19:00

标签: reflection go struct interface slice

我有一个看起来像这样的结构:

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"`
}

在同一个包中,我有一个返回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
}

我是否可以重写此函数,以便它接收查询字符串,同时将BetaKey类型作为interface{},并返回interface{}切片,以便我可以重用代码而不是将其粘贴到每个包中,因为它实际上是相同的,但唯一的区别是更改结构的名称。

这可能吗?这也是建议的吗?如果没有,那么为什么?

2 个答案:

答案 0 :(得分:1)

我用json编写了一个小例子,而不是sql行。您可以尝试从此代码开发:

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

答案 1 :(得分:1)

泛型可以用来实现这样的东西,但Go不支持泛型。要在Go中执行您想要的操作,您需要使用反射。

您可以更改您的函数以获取1个附加参数,例如reflect.Type,它指定应加载各行的值的类型。

然后,您可以使用reflect.New()创建此类型的新值并获取指向它的指针。您可以使用Value.Interface()reflect.Value值获取interface{}类型的指针值。包裹指针的interface{}现在可以传递给Rows.StructScan()

如果您希望结果切片保存非指针值,您可以使用reflect.Indirect()获取指向的值(以及另一个reflect.Interface()以将结构值提取为interface{} )。

示例代码:

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
}

它的核心是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

以下是如何测试它的:

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)

输出:

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>

Go Playground上尝试。

备注:

上述解决方案将返回[]interface{}类型的值,其元素将为 static 类型interface{},其动态类型将是{{{ 1}}参数。例如,如果您使用type:

调用它
reflect.Type

结果切片中的值将包含动态类型bt := reflect.TypeOf(BetaKey{}) ,因此您可以安全地type assert这样:

BetaKey

要了解有关反思的更多信息,请阅读博文:

The Go Blog: The Laws of Reflection