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条)

报告相同问题?

悬赏问题

  • ¥15 ROS Turtlebot3 多机协同自主探索环境时遇到的多机任务分配问题,explore节点
  • ¥15 Matlab怎么求解含参的二重积分?
  • ¥15 苹果手机突然连不上wifi了?
  • ¥15 cgictest.cgi文件无法访问
  • ¥20 删除和修改功能无法调用
  • ¥15 kafka topic 所有分副本数修改
  • ¥15 小程序中fit格式等运动数据文件怎样实现可视化?(包含心率信息))
  • ¥15 如何利用mmdetection3d中的get_flops.py文件计算fcos3d方法的flops?
  • ¥40 串口调试助手打开串口后,keil5的代码就停止了
  • ¥15 电脑最近经常蓝屏,求大家看看哪的问题