reflection - 使用反射和结构来构建通用处理程序函数

标签 reflection go

我在构建可以动态使用参数化结构的函数时遇到一些麻烦。因此,我的代码有 20 多个类似的函数,除了基本上使用的一种类型之外。我的大部分经验都是使用 Java,我只是开发基本的通用函数,或者使用普通对象作为函数的参数(以及从那时起的反射)。我需要类似的东西,使用 Go。

我有几种类型,例如:

// The List structs are mostly needed for json marshalling
type OrangeList struct {
    Oranges []Orange
}

type BananaList struct {
    Bananas []Banana
}

type Orange struct {
    Orange_id string
    Field_1 int
    // The fields are different for different types, I am simplifying the code example
}

type Banana struct {
    Banana_id string
    Field_1 int
    // The fields are different for different types, I am simplifying the code example
}

然后我有函数,基本上针对每种列表类型:

// In the end there are 20+ of these, the only difference is basically in two types! 
// This is very un-DRY!
func buildOranges(rows *sqlx.Rows) ([]byte, error) {
    oranges := OrangeList{}     // This type changes
    for rows.Next() {
        orange := Orange{}      // This type changes
        err := rows.StructScan(&orange)   // This can handle each case already, could also use reflect myself too
        checkError(err, "rows.Scan")
        oranges.Oranges = append(oranges.Oranges,orange)
    }
    checkError(rows.Err(), "rows.Err")
    jsontext, err := json.Marshal(oranges)
    return jsontext, err
}

是的,我可以更改 sql 库以使用更智能的 ORM 或框架,但这不是重点。我想了解如何构建可以为所有不同类型处理类似函数的通用函数。

我已经做到了这一点,但它仍然无法正常工作(我认为目标不是预期的结构):

func buildWhatever(rows *sqlx.Rows, tgt interface{}) ([]byte, error) {
    tgtValueOf := reflect.ValueOf(tgt)
    tgtType := tgtValueOf.Type()
    targets := reflect.SliceOf(tgtValueOf.Type())
    for rows.Next() {
        target := reflect.New(tgtType)
        err := rows.StructScan(&target) // At this stage target still isn't 1:1 smilar struct so the StructScan fails... It's some perverted "Value" object instead. Meh.
        // Removed appending to the list because the solutions for that would be similar
        checkError(err, "rows.Scan")
    }
    checkError(rows.Err(), "rows.Err")
    jsontext, err := json.Marshal(targets)
    return jsontext, err
}

所以嗯,我需要提供列表类型和普通类型作为参数,然后构建每个类型,并且我的其余逻辑可能很容易修复。

最佳答案

事实证明,有一个 sqlx.StructScan(rows, &destSlice) 函数 可以在给定适当类型的 slice 的情况下执行内部循环。 sqlx 文档引用了反射操作的缓存结果,因此与编写相比,它可能有一些额外的优化。

听起来您实际上要问的直接问题是“如何从我的 reflect.Value 中获取 rows.StructScan 会接受的内容?”直接的答案是reflect.Interface(target);它应该返回一个代表 *Orangeinterface{},您可以直接传递给 StructScan(无需额外的 & 操作需要)。然后,我认为 targets = Reflect.Append(targets, target.Indirect()) 会将您的 target 转换为 reflect.Value 代表Orange 并将其附加到 slice 中。 targets.Interface() 应该为您提供一个 interface{} ,表示 []Orangejson.Marshal 可以理解。我说所有这些“应该”和“我认为”是因为我还没有尝试过那条路线。

一般来说,反射是冗长且缓慢的。有时这是完成某件事的最佳或唯一方法,但通常值得寻找一种方法来在没有它的情况下完成任务。

因此,如果它适用于您的应用,您还可以将 Rows 直接转换为 JSON,而无需通过中间结构。这是一个示例程序(当然需要 sqlite3),它将 sql.Rows 转换为 map[string]string,然后转换为 JSON。 (请注意,它不会尝试处理 NULL、将数字表示为 JSON 数字,或者通常处理任何不适合 map[string]string 的内容。)

package main

import (
    _ "code.google.com/p/go-sqlite/go1/sqlite3"

    "database/sql"
    "encoding/json"
    "os"
)

func main() {
    db, err := sql.Open("sqlite3", "foo")
    if err != nil {
        panic(err)
    }
    tryQuery := func(query string, args ...interface{}) *sql.Rows {
        rows, err := db.Query(query, args...)
        if err != nil {
            panic(err)
        }
        return rows
    }
    tryQuery("drop table if exists t")
    tryQuery("create table t(i integer, j integer)")
    tryQuery("insert into t values(?, ?)", 1, 2)
    tryQuery("insert into t values(?, ?)", 3, 1)

    // now query and serialize
    rows := tryQuery("select * from t")
    names, err := rows.Columns()
    if err != nil {
        panic(err)
    }
    // vals stores the values from one row
    vals := make([]interface{}, 0, len(names))
    for _, _ = range names {
        vals = append(vals, new(string))
    }
    // rowMaps stores all rows
    rowMaps := make([]map[string]string, 0)
    for rows.Next() {
        rows.Scan(vals...)
        // now make value list into name=>value map
        currRow := make(map[string]string)
        for i, name := range names {
            currRow[name] = *(vals[i].(*string))
        }
        // accumulating rowMaps is the easy way out
        rowMaps = append(rowMaps, currRow)
    }
    json, err := json.Marshal(rowMaps)
    if err != nil {
        panic(err)
    }
    os.Stdout.Write(json)
}

理论上,您可以通过不每次重复使用相同的 rowMap 并使用 json.Encoder 将每行的 JSON 附加到缓冲区来构建它来减少分配。您可以更进一步,根本不使用 rowMap,而只使用名称和值的列表。我应该说我还没有将速度与基于 reflect 的方法进行比较,尽管我知道 reflect 足够慢,如果您可以忍受的话,可能值得对它们进行比较任一策略。

关于reflection - 使用反射和结构来构建通用处理程序函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20294044/

相关文章:

java - 使用java反射api访问私有(private)对象

reflection - 如何在 F# 中获取模块的类型

java - 为什么 Java 反射调用比方法调用快?

go - 是否有从 ios 日志中出现的字符的术语,如 `\M-C\M-6` 或 `\134`

go - 如何在 golang thrift 服务器中获取客户端的 IP

java - `String.class` 和 `new Class[]{String.class}` 有什么区别?

java - 加载 java 类方法,文档与方法的行为不一致

go - 如何使函数适用于不同的输入类型?

go - 调用 ExecuteTemplate 收到 i/o 超时错误

input - Go - 如何知道输出 channel 何时完成