使代码更通用

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

Making code more generic

问题

我有一个程序,其中许多功能在不同的结构体中是相似的,然而,我不得不一遍又一遍地编写这些函数,尤其是因为内部处理的变量是不同的结构体。

我在这里写了一个示例代码。

在 Go Playgroud 上

package main

import "fmt"

func (a *Match) Add(v Match) {
    a.Runs += v.Runs
    a.Points += v.Points
}

type Match struct {
    Runs   uint64
    Points uint64
}

func (a *Activity) Add(v Activity) {
    a.Walk += v.Walk
    a.Jog += v.Jog
}

type Activity struct {
    Walk uint64
    Jog  uint64
}

func GetDailyMatches() map[string]Match {
    var dailyMatches map[string]Match

    Match1, Match2 := Match{5, 10}, Match{1, 2}
    dailyMatches = make(map[string]Match)
    dailyMatches["01"] = Match1
    dailyMatches["02"] = Match2
    dailyMatches["03"] = Match1
    dailyMatches["04"] = Match2
    return dailyMatches
}

func GetDailyActivities() map[string]Activity {
    var dailyActivities map[string]Activity

    Activity1, Activity2 := Activity{5, 10}, Activity{1, 2}
    dailyActivities = make(map[string]Activity)
    dailyActivities["01"] = Activity1
    dailyActivities["02"] = Activity2
    dailyActivities["03"] = Activity1
    dailyActivities["04"] = Activity2
    return dailyActivities
}

func main() {
    fmt.Println(CalculateMatchSummary("01", "03"))
    fmt.Println(CalculateActivitySummary("02", "04"))
    fmt.Println(CalculateMatchSummary("01", "03"))
    fmt.Println(CalculateActivitySummary("02", "04"))
}

func CalculateMatchSummary(start, end string) (total Match) {
    dailyMatches := GetDailyMatches()
    for day, value := range dailyMatches {
        if day < start {
            continue
        } else if day > end {
            continue
        } else {
            total.Add(value)
        }
    }
    return
}

func CalculateActivitySummary(start, end string) (total Activity) {
    dailyActivities := GetDailyActivities()
    for day, value := range dailyActivities {
        if day < start {
            continue
        } else if day > end {
            continue
        } else {
            total.Add(value)
        }
    }
    return
}

如果你注意到,MatchActivity 都有相同的函数和相同的结构体,只是在内部它们是不同的结构体。

在 Golang 中有没有一种简单的方法使代码更通用(Go 泛型,在 Golang 中并不存在)?

英文:

I have a program where many functionalities are similar across different structures, however, I end up writing these functions again and again, esp because the variable that are being dealt inside are of different structures.

I have written a sample code here.

In Go Playgroud

package main

import &quot;fmt&quot;

func (a *Match) Add(v Match) {
	a.Runs += v.Runs
	a.Points += v.Points
}

type Match struct {
	Runs   uint64
	Points uint64
}

func (a *Activity) Add(v Activity) {
	a.Walk += v.Walk
	a.Jog += v.Jog
}

type Activity struct {
	Walk uint64
	Jog  uint64
}

func GetDailyMatches() map[string]Match {
	var dailyMatches map[string]Match

	Match1, Match2 := Match{5, 10}, Match{1, 2}
	dailyMatches = make(map[string]Match)
	dailyMatches[&quot;01&quot;] = Match1
	dailyMatches[&quot;02&quot;] = Match2
	dailyMatches[&quot;03&quot;] = Match1
	dailyMatches[&quot;04&quot;] = Match2
	return dailyMatches
}

func GetDailyActivities() map[string]Activity {
	var dailyActivities map[string]Activity

	Activity1, Activity2 := Activity{5, 10}, Activity{1, 2}
	dailyActivities = make(map[string]Activity)
	dailyActivities[&quot;01&quot;] = Activity1
	dailyActivities[&quot;02&quot;] = Activity2
	dailyActivities[&quot;03&quot;] = Activity1
	dailyActivities[&quot;04&quot;] = Activity2
	return dailyActivities
}

func main() {
	fmt.Println(CalculateMatchSummary(&quot;01&quot;, &quot;03&quot;))
	fmt.Println(CalculateActivitySummary(&quot;02&quot;, &quot;04&quot;))
	fmt.Println(CalculateMatchSummary(&quot;01&quot;, &quot;03&quot;))
	fmt.Println(CalculateActivitySummary(&quot;02&quot;, &quot;04&quot;))
}

func CalculateMatchSummary(start, end string) (total Match) {
	dailyMatches := GetDailyMatches()
	for day, value := range dailyMatches {
		if day &lt; start {
			continue
		} else if day &gt; end {
			continue
		} else {
			total.Add(value)
		}
	}
	return
}

func CalculateActivitySummary(start, end string) (total Activity) {
	dailyActivities := GetDailyActivities()
	for day, value := range dailyActivities {
		if day &lt; start {
			continue
		} else if day &gt; end {
			continue
		} else {
			total.Add(value)
		}
	}
	return
}

If you notice, both Match and Activity has the same functions and same structures, except that internally they are of different structures.

Is there a easy way to make the code more generic (Go generics, which is not there in Go??) in Golang itself.

答案1

得分: 1

Go语言有一个很好的包叫做"reflect"。严格来说,你不能实现泛型,但你可以通过相同的行为来统一代码。
我稍微修改了你的playground:https://play.golang.org/p/bfqZsFOgVQ

主要部分:

func AddTwo(a, b interface{}) interface{} {
    va := reflect.ValueOf(a)
    vb := reflect.ValueOf(b)
    res := reflect.New(reflect.TypeOf(a)).Elem()
    if va.Kind() != reflect.Struct && vb.Kind() != reflect.Struct {
        return nil
    }

    na, nb := va.NumField(), vb.NumField()
    if na != nb {
        return nil
    }

    for i := 0; i < na; i++ {
        // 需要进行额外的验证
        fa := va.Field(i).Uint()
        fb := vb.Field(i).Uint()
        fr := fa + fb
        res.Field(i).SetUint(fr)
    }

    return res.Interface()
}

我使用反射来检查给定结构体的字段。如果两个字段都是uint64类型,我可以通过反射将它们相加。如果你的结构体包含多个uint64字段,它可以将它们全部相加!

请注意,在调用此函数后,您必须将返回的接口转换为给定结构体的类型。这就是为什么它不是严格的泛型,因为返回类型是一个接口,而不是Match或Activity。

编辑:甚至不需要返回一个新的结构体。你可以通过调用.SetUint()方法来简单地更新"a"结构体的字段。

英文:

Go has a pretty package "reflect". You can not do genericity strictly speaking but you can get unification of code for the same behavior.
I've changed your playground a bit : https://play.golang.org/p/bfqZsFOgVQ

The main part :

func AddTwo(a, b interface{}) interface{} {
	va := reflect.ValueOf(a)
	vb := reflect.ValueOf(b)
	res := reflect.New(reflect.TypeOf(a)).Elem()
	if va.Kind() != reflect.Struct &amp;&amp; vb.Kind() != reflect.Struct {
		return nil
	}

	na, nb := va.NumField(), vb.NumField()
	if na != nb {
		return nil
	}

	for i := 0; i &lt; na; i++ {
		// additional verification needed here
		fa := va.Field(i).Uint()
		fb := vb.Field(i).Uint()
		fr := fa + fb
		res.Field(i).SetUint(fr)
	}

	return res.Interface()
}

I use reflect to check the fields of the struct I am given. If both are uint64, I can add them reflectively. If your structs contains many uint64, it can add them all !

Note that you must convert the resulting interface to the type of the struct given after calling this function. That is why this is not strictly generic, because the returning type is a interface, and not a Match or Activity.

EDIT: No need even to return a new struct. You can simply update the field of the "a" struct by calling .SetUint() method.

huangapple
  • 本文由 发表于 2015年10月16日 23:26:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/33174148.html
匿名

发表评论

匿名网友

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

确定