我正在学习Golang并希望了解解决这个问题的"Go way".
具体来说,我正在使用该sql软件包,我在代码中看到了一些冗余功能,我想将其转换为函数.
我有,1)用户结构:
type User struct {
ID int
FirstName string
LastName string
}
Run Code Online (Sandbox Code Playgroud)
2)从数据库中获取ID的一个用户的函数(Postgresql):
func GetUserById(id int) (user User) {
sql := `
SELECT id, first_name, last_name
FROM users
WHERE id = $1
`
row := db.QueryRow(sql, id)
err := row.Scan(&user.ID, &user.FirstName, &user.LastName)
if err != nil {
panic(err)
}
return
}
Run Code Online (Sandbox Code Playgroud)
并且,3)用于获取数据库中的所有用户的功能:
func GetUsers() (users []User) {
sql := `
SELECT id, first_name, last_name
FROM users
ORDER BY last_name
`
rows, err := db.Query(sql)
if err != nil {
panic(err)
}
for rows.Next() {
user := User{}
err := rows.Scan(&user.ID, &user.FirstName, &user.LastName)
if err != nil {
panic(err)
}
users = append(users, user)
}
rows.Close()
return
}
Run Code Online (Sandbox Code Playgroud)
在用户记录中只有3个字段,这是一个简单的例子.但是,有了更多的字段,这rows.Scan(...)两个数据访问函数都可以很好地转移到一个可以调用的函数:
func ScanUserFromRow(row *sql.Row) (user User) {
err := row.Scan(&user.ID, &user.FirstName, &user.LastName)
if err != nil {
panic(err)
}
return
}
Run Code Online (Sandbox Code Playgroud)
然后更新的数据库访问函数看起来像:
func GetUserById(id int) (user User) {
sql := `
SELECT id, first_name, last_name
FROM users
WHERE id = $1
`
row := db.QueryRow(sql, id)
user = ScanUserFromRow(row)
return
}
func GetUsers() (users []User) {
sql := `
SELECT id, first_name, last_name
FROM users
ORDER BY last_name
`
rows, err := db.Query(sql)
if err != nil {
panic(err)
}
for rows.Next() {
user := ScanUserFromRow(rows)
users = append(users, user)
}
rows.Close()
return
}
Run Code Online (Sandbox Code Playgroud)
但是,在GetUserById函数的情况下,我正在处理*sql.Row结构指针.在GetUsers函数的情况下,我正在处理*sql.Rows结构指针.两者是不同的......显然,但两者相似,因为它们都有一种Scan方法.
似乎类型系统不会让我创建一个接受其中一个的方法.有没有办法利用这个interface{},还是有其他更惯用的Go解决方案呢?
带着这个疑问,我说都sql.Row和sql.Rows是鸭子,认为"江湖"有Scan.如何使用允许两者的函数参数?
@seh提供了一个下面的答案,允许通过使参数成为自定义接口来实现我希望的那种鸭子类型.这是结果代码:
type rowScanner interface {
Scan(dest ...interface{}) error
}
func ScanPlayerFromRow(rs rowScanner) (u User) {
err := rs.Scan(&u.ID, &u.FirstName, &u.LastName)
if err != nil {
panic(err)
}
return
}
Run Code Online (Sandbox Code Playgroud)
...或者,正如@Kaveh在下面指出的那样,接口的定义可以在函数参数中内联:
func ScanPlayerFromRow(rs interface {
Scan(des ...interface{}) error
}) (u User) {
err := rs.Scan(&u.ID, &u.FirstName, &u.LastName)
if err != nil {
panic(err)
}
return
}
Run Code Online (Sandbox Code Playgroud)
两者sql.Rows都有方法。sql.Row 标准库中没有包含该方法的接口,但可以自己定义它:Scan
type rowScanner interface {
Scan(dest ...interface{}) error
}
Run Code Online (Sandbox Code Playgroud)
rowScanner然后,您可以编写一个对 a而不是 a*sql.Row或 a进行操作的函数*sql.Rows:
import "database/sql"
type rowScanner interface {
Scan(dest ...interface{}) error
}
func handleRow(scanner rowScanner) error {
var i int
return scanner.Scan(&i)
}
func main() {
var row *sql.Row
handleRow(row) // Crashes due to calling on a nil pointer.
var rows *sql.Rows
handleRow(rows) // Crashes due to calling on a nil pointer.
}
Run Code Online (Sandbox Code Playgroud)
我没有使用真实的*sql.Rowor进行模拟*sql.Rows,但这应该会给您带来想法。您想要的ScanUserFromRow功能需要 arowScanner而不是*sql.Row。