在Go语言中编写通用的数据访问函数

huangapple go评论70阅读模式
英文:

Writing generic data access functions in Go

问题

我正在编写一个允许从数据库访问数据的代码。然而,我发现自己在类似的类型和字段上重复相同的代码。我该如何编写通用的函数来实现相同的功能?

例如,我想要实现的功能是...

type Person{FirstName string}
type Company{Industry string}

getItems(typ string, field string, val string) ([]interface{}) {
    ...
}

var persons []Person
persons = getItems("Person", "FirstName", "John")

var companies []Company
cs = getItems("Company", "Industry", "Software")
英文:

I'm writing code that allows data access from a database. However, I find myself repeating the same code for similar types and fields. How can I write generic functions for the same?

e.g. what I want to achieve ...

type Person{FirstName string}
type Company{Industry string}

getItems(typ string, field string, val string) ([]interface{}) {
    ...
}

var persons []Person
persons = getItems("Person", "FirstName", "John")

var companies []Company
cs = getItems("Company", "Industry", "Software")

答案1

得分: 2

所以你在返回一个nil接口类型的切片的想法上肯定是正确的。然而,当你尝试访问特定成员或调用特定方法时,你会遇到问题,因为你不知道你要找的类型是什么。这就是类型断言非常有用的地方。稍微扩展一下你的代码:

getPerson(typ string, field string, val string) []Person {
    slice := getItems(typ, field, val)
    output := make([]Person, 0)
    i := 0
    for _, item := range slice {
        // 类型断言!
        thing, ok := item.(Person)
        if ok {
            output = append(output, thing)
            i++
        }
    }
    return output
}

这样做的作用是执行一个通用的搜索,然后只筛选出正确类型的项。具体来说,类型断言:

thing, ok := item.(Person)

检查变量item是否是类型Person,如果是,它返回该值和true,否则返回nil和false(因此通过检查ok可以知道断言是否成功)。

实际上,如果你愿意,你可以进一步定义getItems()函数,以另一个布尔函数为基础。基本上,想法是让getItems()在数据库的每个元素上运行传递给它的函数,并且只有在对元素运行函数返回true时才将该元素添加到结果中:

getItem(critera func(interface{})bool) []interface{} {
    output := make([]interface{}, 0)
    foreach _, item := range database {
        if criteria(item) {
            output = append(output, item)
        }
    }
}

(老实说,如果是我,我会将两者结合起来,接受一个条件函数,但也接受字段和值字符串)

英文:

So you're definitely on the right track with the idea of returning a slice of nil interface types. However, you're going to run into problems when you try accessing specific members or calling specific methods, because you're not going to know what type you're looking for. This is where type assertions are going to come in very handy. To extend your code a bit:

getPerson(typ string, field string, val string) []Person {
    slice := getItems(typ, field, val)
    output := make([]Person, 0)
    i := 0
    for _, item := range slice {
        // Type assertion!
        thing, ok := item.(Person)
        if ok {
            output = append(output, thing)
            i++
        }
    }
    return output
}

So what that does is it performs a generic search, and then weeds out only those items which are of the correct type. Specifically, the type assertion:

thing, ok := item.(Person)

checks to see if the variable item is of type Person, and if it is, it returns the value and true, otherwise it returns nil and false (thus checking ok tells us if the assertion succeeded).

You can actually, if you want, take this a step further, and define the getItems() function in terms of another boolean function. Basically the idea would be to have getItems() run the function pass it on each element in the database and only add that element to the results if running the function on the element returns true:

getItem(critera func(interface{})bool) []interface{} {
    output := make([]interface{}, 0)
    foreach _, item := range database {
        if criteria(item) {
            output = append(output, item)
        }
    }
}

(honestly, if it were me, I'd do a hybrid of the two which accepts a criteria function but also accepts the field and value strings)

答案2

得分: 2

joshlf13有一个很好的答案。我想进一步扩展一下,以保持一些额外的类型安全性。我会使用一个收集器函数,而不是一个条件函数。

// 声明一个有类型的输出数组,没有接口
output := []string{}

// 收集器函数,根据需要填充输出数组
func collect(i interface{}) {
// 程序中唯一不安全的部分限制在这个函数中
if val, ok := i.(string); ok {
output = append(output, val)
}
}

// getItem函数使用收集器
func getItem(collect func(interface{})) {
for _, item := range database {
collect(item)
}
}

getItem(collect) // 执行我们的获取操作,并从上面填充输出数组

这样做的好处是不需要在调用getItems后再次遍历interface{}切片并进行类型转换。

英文:

joshlf13 has a great answer. I'd expand a little on it though to maintain some additional type safety. instead of a critera function I would use a collector function.

// typed output array no interfaces
output := []string{}

// collector that populates our output array as needed
func collect(i interface{}) {
 // The only non typesafe part of the program is limited to this function
 if val, ok := i.(string); ok {
   output = append(output, val) 
 }
}

// getItem uses the collector  
func getItem(collect func(interface{})) {
    foreach _, item := range database {
        collect(item)
    }
}

getItem(collect) // perform our get and populate the output array from above.

This has the benefit of not requiring you to loop through your interface{} slice after a call to getItems and do yet another cast.

huangapple
  • 本文由 发表于 2012年8月11日 20:29:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/11914712.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定