Golang避免代码重复

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

Golang avoiding code duplication

问题

我目前有两个结构体。

type One struct {
    Name  string
    Age   int
    ID    int
    Owner string
}

type Two struct {
    ID    int
    Make  string
    Bags  string
    Age   int
    Owner string
}

这些结构体映射到数据库中的一张表,我使用一个接口来提供对数据库和内容的访问。在这种情况下,根据所有者列出 One 和 Two 的数据。

type dbInterface interface {
    ListOnesByOwner(owner string) ([]*One, error)
    ListTwosByOwner(owner string) ([]*Two, error)
}

列出函数除了结构体不同之外都是相同的。

func (db *DB) ListOnes(owner string) ([]*One, error) {
    ones := make([]*One, 0)
    q := db.NewQuery("One").
        Filter("Owner =", owner).
        Order("Name")

    keys, err := db.client.GetAll(q, &ones)
    for i, k := range keys {
        ones[i].ID = k.ID
    }
    return ones, nil
}

func (db *DB) ListTwos(owner string) ([]*Two, error) {
    twos := make([]*Two, 0)
    q := db.NewQuery("Two").
        Filter("Owner =", owner).
        Order("Name")

    keys, err := db.client.GetAll(q, &twos)
    for i, k := range keys {
        twos[i].ID = k.ID
    }
    return twos, nil
}
func main() {
    ones, err := DB.ListOnesByOwner(user.ID)
    twos, err := DB.ListTwosByOwner(user.ID)
}

我对GO还比较新,所以我想知道如何以惯用方式减少这里的代码重复?如果我要添加更多的结构体,那么代码重复的量将会很大。

谢谢任何帮助!

英文:

I have two structs at the moment.

type One struct {
	Name  string
	Age   int
	ID	  int
	Owner string
}

type Two struct {
	ID	  int
	Make  string
	Bags  string
	Age	  int
	Owner string
}

These structs map to a table in a DB, I use an interface to provide access to the DB and contents. In this case just a listing of the data in One and Two based on the owner.

type dbInterface interface {
	ListOnesByOwner(owner string) ([]*One, error)
	LitsTwosByOwner(owner string) ([]*Two, error)
}

The listing functions are the same except for the structs.

func (db *DB) ListOnes(owner string) ([]*One, error) {
	ones = make([]*One, 0)
	q := db.NewQuery("One").
		Filter("Owner =", owner).
		Order("Name")

	keys, err := db.client.GetAll(q, &ones)
	for i, k := range keys {
		ones[i].ID = k.ID
	}
	return ones, nil
}

func (db *DB) ListTwos(owner string) ([]*Two, error) {
	twos = make([]*Two, 0)
	q := db.NewQuery("Two").
		Filter("Owner =", owner).
		Order("Name")

	keys, err := db.client.GetAll(q, &twos)
	for i, k := range keys {
		twos[i].ID = k.ID
	}
	return twos, nil
}

func main() {
	ones, err := DB.ListOnesByOwner(user.ID)
	twos, err := DB.ListTwosByOwner(user.ID)
}

I'm fairly new to GO, so I'm wondering what is the idiomatic way to reduce the code duplication seen here? If I was to add a couple more structs then it would be unwieldy because the amount of code duplication needed.

Thanks for any help!

答案1

得分: 1

假设db.client.GetAll的第二个参数是interface{},看起来是这样的,你实际上可以将其DRY(Don't Repeat Yourself)化简:

func (db *DB) dryGet(owner, table string, result interface{}) error {
    q := db.NewQuery(table).Filter("Owner =", owner).Order("Name")
    keys, err := db.client.GetAll(q, &result)
    return err
}

将结果转换为映射(map)稍微有些困难,因为Go语言缺乏泛型,并且你的结构体没有可以用于接口的方法。这是可能的,但至少需要在每个类型上创建一个getID方法,创建一个hasID接口,然后返回一个map[int]hasID,然后调用者需要将值强制转换回结构体类型以访问其他字段。这并不是最理想的解决方案,但是可行的。然而,上述解决方案至少可以帮助你消除大部分重复的代码。

英文:

Assuming that db.client.GetAll takes an interface{} as its second argument, which it appears to, you can in fact DRY it out:

func (db *DB) dryGet(owner, table string, result interface{}) error {
    q := db.NewQuery(table).Filter("Owner =", owner).Order("Name")
    keys,err := db.client.GetAll(q, &result)
    return err
}

Converting the result to a map is a little more difficult because Go lacks generics, and your structs have no methods that could be used to interface them. It's possible but would require, at the least, creating a getID method on each type, creating a hasID interface, and then returning a map[int]hasID, which the caller would then have to cast the values of back to the struct type to access any other fields. Not optimal, but doable. However, the above solution would at least let you eliminate a good portion of duplicate code.

答案2

得分: 1

只是为了补充接受的答案,如果你正在使用google.golang.org/appengine/datastore,你不需要循环遍历键,除非你想要。

GetAll文档中可以看到(重点在于):

> GetAll在给定的上下文中运行查询,并返回与该查询匹配的所有键,并将值附加到dst

因此,你可以简化你的两个方法,像这样:

func (db *DB) ListOnes(owner string) ([]*One, error) {
	ones = make([]*One, 0)
	q := db.NewQuery("One").
		Filter("Owner =", owner).
		Order("Name")

	if _, err := db.client.GetAll(q, &ones); err != nil {
		return nil, err
	}
	return ones, nil
}

func (db *DB) ListTwos(owner string) ([]*Two, error) {
	twos = make([]*Two, 0)
	q := db.NewQuery("Two").
		Filter("Owner =", owner).
		Order("Name")

	if _, err := db.client.GetAll(q, &twos); err != nil {
		return nil, err
	}
	return twos, nil
}

这仍然有很多重复,所以你现在可以使用接受的答案中的方法来泛化你的代码,例如:

type dbInterface interface {
    ListByType(owner, typ string, dst interface{}) ([]*datastore.Key, error)
}

func (db *DB) ListByType(owner, typ string, dst interface{}) ([]*datastore.Key, error) {
	q := db.NewQuery(typ).
		Filter("Owner =", owner).
		Order("Name")

	return db.client.GetAll(q, dst)
}

你可以像这样使用该实现:

func main() {
    // 如果你不需要键,可以忽略它们
    ones := []*One{}
    if _, err := DB.ListByType(user.ID, "One", &ones); err != nil {
        panic(err)
    }
    
    // 如果你需要键,可以使用它们
    twos := []*Two{}
    keys, err := DB.ListByType(user.ID, "Two", &twos)
    if err != nil {
        panic(err)
    }
}

顺便说一下,如果你想控制如何从数据存储加载你的类型OneTwo等,你可以让它们实现PropertyLoadSaver接口。

英文:

Just to add to the accepted answer, if you're using google.golang.org/appengine/datastore you don't need to loop over the keys, unless you want to.

From the GetAll docs: (emphasis mine)

> GetAll runs the query in the given context and returns all keys that
> match that query, as well as appending the values to dst.

So you could simplify your two methods to something like this:

func (db *DB) ListOnes(owner string) ([]*One, error) {
	ones = make([]*One, 0)
	q := db.NewQuery("One").
		Filter("Owner =", owner).
		Order("Name")

	if _, err := db.client.GetAll(q, &ones); err != nil {
		return nil, err
	}
	return ones, nil
}

func (db *DB) ListTwos(owner string) ([]*Two, error) {
	twos = make([]*Two, 0)
	q := db.NewQuery("Two").
		Filter("Owner =", owner).
		Order("Name")

	if _, err := db.client.GetAll(q, &twos); err != nil {
		return nil, err
	}
	return twos, nil
}

This is still quite a lot of duplication, so you could now generalize your code with an approach like in the accepted answer, e.g.:

type dbInterface interface {
    ListByType(owner, typ string, dst interface{}) ([]*datastore.Key, error)
}

func (db *DB) ListByType(owner, typ string, dst interface{}) ([]*datastore.Key, error) {
	q := db.NewQuery(typ).
		Filter("Owner =", owner).
		Order("Name")

	return db.client.GetAll(q, dst)
}

And you can use that implementation like this:

func main() {
    // ignore keys if you don't need them
    ones := []*One{}
    if _, err := DB.ListByType(user.ID, "One", &ones); err != nil {
        panic(err)
    }
    
    // use keys if you need them
    twos := []*Two{}
    keys, err := DB.ListByType(user.ID, "Two", &twos)
    if err != nil {
        panic(err)
    }
}

And by the way, if you want to control how your types, One and Two etc., are loaded from the datastore, you could have them implement the PropertyLoadSaver interface.

huangapple
  • 本文由 发表于 2017年4月18日 01:34:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/43456418.html
匿名

发表评论

匿名网友

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

确定