创建一个接受具有相同方法的两个不同对象的函数

Ell*_*son 6 go

我正在学习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.Rowsql.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)

seh*_*seh 3

两者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