duanji9311 2016-05-09 06:18
浏览 29
已采纳

如何使用接口允许一种以上的结构类型使代码更可用?

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:

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.

Is this possible? And also is this advised? If not, then why?

  • 写回答

2条回答 默认 最新

  • drxvjnx58751 2016-05-09 09:25
    关注

    Generics could be used to implement such thing, but Go does not support generics. To do what you want in Go, you need to use reflection.

    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.

    Then you can use reflect.New() to create a new value of this type and acquire a pointer to it. You can use Value.Interface() to obtain the pointer value as a type of interface{} from the reflect.Value value. This interface{} wrapping the pointer can now be passed to 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{}).

    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:

    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
    ", r[0], r, err)
    
    r, err = buildResults("", reflect.TypeOf(BetaKey{}))
    fmt.Printf("%T %+v %v
    ", 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.

    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. 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:

    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

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥100 set_link_state
  • ¥15 虚幻5 UE美术毛发渲染
  • ¥15 CVRP 图论 物流运输优化
  • ¥15 Tableau online 嵌入ppt失败
  • ¥100 支付宝网页转账系统不识别账号
  • ¥15 基于单片机的靶位控制系统
  • ¥15 真我手机蓝牙传输进度消息被关闭了,怎么打开?(关键词-消息通知)
  • ¥15 装 pytorch 的时候出了好多问题,遇到这种情况怎么处理?
  • ¥20 IOS游览器某宝手机网页版自动立即购买JavaScript脚本
  • ¥15 手机接入宽带网线,如何释放宽带全部速度