【问题标题】:Golang avoiding code duplicationGolang 避免代码重复
【发布时间】:2017-04-17 17:34:29
【问题描述】:

目前我有两个结构。

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

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

这些结构映射到数据库中的表,我使用接口来提供对数据库和内容的访问。在这种情况下,只是基于所有者的一号和二号中的数据列表。

type dbInterface interface {
    ListOnesByOwner(owner string) ([]*One, error)
    LitsTwosByOwner(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 还很陌生,所以我想知道减少此处看到的代码重复的惯用方法是什么?如果我要添加更多的结构,那将是笨拙的,因为需要大量的代码重复。

感谢您的帮助!

【问题讨论】:

  • 在我发布答案之前,我有一个问题。 db.client.GetAll 的第二个参数是 inteface{} 吗?
  • 我投票结束这个问题,因为这个问题属于codereview.stackexchange.com
  • @PaulHankin 他不是要求进行一般性的代码审查,而是询问有关如何使用 Go 语言的具体问题。对我来说似乎是合法的。
  • 您能否分享一下您正在使用的实现db.client.GetAll 方法/功能的包?从外观上看,您可能根本不需要遍历键,因为您已经传递了一个指向“目标”值的指针......因此我假设GetAll 为您填写了它的第二个参数并且返回的键仅供参考,不一定非要使用...例如google.golang.org/appengine/datastore 非常相似的*Query.GetAll 方法就是这样工作的。
  • @mkopriva 我正在使用数据存储包

标签: go code-duplication


【解决方案1】:

假设db.client.GetAllinterface{} 作为它的第二个参数,它看起来是这样的,你实际上可以把它干掉:

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
}

将结果转换为映射有点困难,因为 Go 缺少泛型,并且您的结构没有可用于接口它们的方法。这是可能的,但至少需要在每种类型上创建一个getID 方法,创建一个hasID 接口,然后返回一个map[int]hasID,然后调用者必须将其值转换回结构键入以访问任何其他字段。不是最优的,但可行。但是,上述解决方案至少可以让您消除很大一部分重复代码。

【讨论】:

    【解决方案2】:

    只是添加到已接受的答案中,如果您使用的是 google.golang.org/appengine/datastore,则不需要循环键,除非您愿意。

    来自GetAll docs:(强调我的)

    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() {
        // 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)
        }
    }
    

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

    【讨论】:

      猜你喜欢
      • 2014-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多