如果你真的想在 Go 中做泛型,你需要一个接口。该接口将通过两种类型实现,一种用于团队,一种用于玩家。score 函数将这个接口类型的对象作为参数,并在不知道它是与球队还是球员一起工作的情况下实现公共评分逻辑。这就是概述。以下是一些细节:
接口的方法集正是评分函数需要的功能。让我们从它似乎需要的两种方法开始,
type scoreable interface {
stats(scID) StatLine // get statLine for ID
score(scID, int) // set score for ID
}
和一个通用的可评分 ID 类型,
type scID int
可以实现此接口的类型不是 TeamID 和 PlayerID,而是保存它们的映射的类型。此外,这些类型中的每一种都需要地图、StatLine 和分数地图。结构适用于此:
type teamScores struct {
stats map[TeamID]StatLine
scores map[TeamID]int
}
然后实施 scoreable,
func (s *teamScores) stats(id scID) StatLine {
return s.stats[TeamID(id)]
}
func (s *teamScores) score(id scID, sc int) {
s.scores[TeamID(id)] = sc
}
等等,你可能会说。scID 到 TeamID 的那种类型转换。那安全吗?我们是否还不如采用甚至没有 TeamID 的低工程方法?好吧,只要明智地使用这些方法,它就是安全的。struct teamScores 将一个 TeamID 映射与另一个 TeamID 映射相关联。我们即将编写的通用函数 score 将此结构作为参数,因此被赋予正确的关联。它不可能混淆 TeamID 和 PlayerID。这很有价值,并且在足够大的程序中可以证明这种技术是合理的。
对 PlayerID 执行相同的操作,定义一个类似的结构并添加两个方法。
写一次 score 函数。这样开始:
func score(s scoreable) {
for
糟糕,我们需要一些方法来迭代。一个权宜之计的解决方案是获取 ID 列表。让我们添加该方法:
type scoreable interface {
ids() []scID // get list of all IDs
stats(scID) StatLine // get statLine for ID
score(scID, int) // set score for ID
}
以及 teamScores 的实现:
func (s *teamScores) ids() (a []scID) {
for tid := range s.stats {
a = append(a, scID(tid))
}
return
}
现在我们在哪里?
func score(s scoreable) {
// lets say we need some intermediate value
sum := make(map[scID]float64)
// for each id for which we have a statLine,
for _, id := range s.ids() { // note method call
// get the statLine
stats := s.stats() // method call
// compute intermediate value
sum[id] = 0.
for _, statValue := range stats {
sum[id] += statValue
}
}
// now compute the final scores
for id, s := range sum {
score := int(s) // stub computation
s.score(id, score) // method call
}
}
请注意,该函数采用接口类型 scoreable,而不是像 *scoreable 这样的接口指针。一个接口可以保存指针类型 *teamScores。没有额外的指向接口的指针是合适的。
最后,为了调用通用函数,我们需要一个 teamScores 类型的对象。您可能已经拥有联赛统计数据,但可能尚未创建得分图。您可以像这样一次完成所有这些操作:
ts := &teamScores{
stats: ...your existing map[TeamID]Statline,
scores: make(map[TeamID]int),
}
称呼:
score(ts)
并且团队分数将在 ts.scores 中。