dragon0023 2016-05-28 23:35
浏览 73
已采纳

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

I'm learning Golang and want to understand the "Go way" of solving this problem.

Specifically, I'm using the sql package and I'm seeing some redundant functionality in my code that I'd like to pull out into a function.

My Original Code

I have, 1) a user struct:

type User struct {
  ID        int
  FirstName string
  LastName  string
}

2) a function for getting one user by ID from the database (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
}

and, 3) a function for getting all users from the database:

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
}

Desired Refactor

With only 3 fields in the user record, this is a trivial example. But, with a few more fields, the rows.Scan(...) piece of both data access functions would be nice to move to a function that both could call:

func ScanUserFromRow(row *sql.Row) (user User) {
  err := row.Scan(&user.ID, &user.FirstName, &user.LastName)
  if err != nil {
    panic(err)
  }
  return
}

Then the updated database access functions would look something like:

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
}

However, in the case of the GetUserById function, I'm dealing with an *sql.Row struct pointer. In the case of the GetUsers function, I'm dealing with an *sql.Rows struct pointer. The two are different... obviously, but similar in that they both have a Scan method.

It seems the type system won't let me create a method that will accept one or the other. Is there a way to do this utilizing interface{}, or is there some other more idiomatic Go solution for this?

Update/Solution

With this question, I'm saying both sql.Row and sql.Rows are ducks that "quack" with Scan. How can I use a function argument that allows both?

@seh provided an answer below that allows the sort of duck-typing I was hoping for by making the argument a custom interface. Here is the resulting code:

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
}

...or, as @Kaveh pointed out below, the definition of the interface can be inlined in the function argument:

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
}
  • 写回答

3条回答 默认 最新

  • duanbinren8906 2016-05-29 00:21
    关注

    Both sql.Rows and sql.Row have a Scan method. There's no interface containing that method in the standard library, but it's possible to define it yourself:

    type rowScanner interface {
        Scan(dest ...interface{}) error
    }
    

    You can then write a function that operates on a rowScanner rather than a *sql.Row or 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.
     }
    

    I didn't mock up using a real *sql.Row or *sql.Rows, but that should give you the idea. Your desired ScanUserFromRow function would demand a rowScanner instead of *sql.Row.

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

报告相同问题?

悬赏问题

  • ¥20 测距传感器数据手册i2c
  • ¥15 RPA正常跑,cmd输入cookies跑不出来
  • ¥15 求帮我调试一下freefem代码
  • ¥15 matlab代码解决,怎么运行
  • ¥15 R语言Rstudio突然无法启动
  • ¥15 关于#matlab#的问题:提取2个图像的变量作为另外一个图像像元的移动量,计算新的位置创建新的图像并提取第二个图像的变量到新的图像
  • ¥15 改算法,照着压缩包里边,参考其他代码封装的格式 写到main函数里
  • ¥15 用windows做服务的同志有吗
  • ¥60 求一个简单的网页(标签-安全|关键词-上传)
  • ¥35 lstm时间序列共享单车预测,loss值优化,参数优化算法