go - 如何将 int 和 string 集合的常用方法重构为一个公共基础?
问题描述
考虑下面的程序,它定义了两种类型IntSet
和分别StringSet
包含int
s 和string
s 的集合。
这些类型的Add()
、AddRange()
、Contains()
、ContainsAny
和Length()
基本相同(仅参数类型不同)。
我可以定义独立的函数Add()
, AddRange()
, ... 没有方法接收器和带有or的interface{}
参数,但我希望这些方法与集合保持耦合。IntSet
StringSet
如果我使用composition,则基本结构无法访问map[...]bool
子结构的 。
重构上述五种方法以消除代码重复的正确方法是什么?
程序:
package main
import (
"fmt"
"sort"
"strconv"
"strings"
)
type IntSet map[int]bool
type StringSet map[string]bool
func NewStringSet(vs []string) StringSet {
ss := StringSet{}
for _, v := range vs {
ss.Add(v)
}
return ss
}
func (ss StringSet) Add(v string) bool {
_, found := ss[v]
ss[v] = true
return !found
}
func (ss StringSet) AddRange(vs []string) {
for _, v := range vs {
ss[v] = true
}
}
func (ss StringSet) Contains(v string) bool {
_, found := ss[v]
return found
}
func (ss StringSet) ContainsAny(vs []string) bool {
for _, v := range vs {
if _, found := ss[v]; found {
return true
}
}
return false
}
func (ss StringSet) Length() int {
return len(ss)
}
func (ss StringSet) Stringify() string {
vs := make([]string, len(ss))
i := 0
for v := range ss {
vs[i] = v
i++
}
return strings.Join(vs, ",")
}
func NewIntSet(vs []int) IntSet {
is := IntSet{}
for _, v := range vs {
is.Add(v)
}
return is
}
func (is IntSet) Add(v int) bool {
_, found := is[v]
is[v] = true
return !found
}
func (is IntSet) AddRange(vs []int) {
for _, v := range vs {
is[v] = true
}
}
func (is IntSet) Contains(v int) bool {
_, found := is[v]
return found
}
func (is IntSet) ContainsAny(vs []int) bool {
for _, v := range vs {
if _, found := is[v]; found {
return true
}
}
return false
}
func (is IntSet) Length() int {
return len(is)
}
func (is IntSet) Stringify() string {
vs := make([]int, 0)
for v := range is {
vs = append(vs, v)
}
sort.Ints(vs)
ws := make([]string, 0)
for v := range vs {
s := strconv.Itoa(v)
ws = append(ws, s)
}
return strings.Join(ws, ",")
}
解决方案
只需保留重复的代码。就维护开销而言,有五种方法不是问题。
无论如何,这是一个带有泛型的强制性示例,它也适用于Go2 操场:
package main
import (
"fmt"
)
type Set[T comparable] map[T]bool
func NewSet[T comparable](vs []T) Set[T] {
ss := Set[T]{}
for _, v := range vs {
ss.Add(v)
}
return ss
}
func (s Set[T]) Add(v T) bool {
_, found := s[v]
s[v] = true
return !found
}
func (s Set[T]) AddRange(vs []T) {
for _, v := range vs {
s[v] = true
}
}
func (s Set[T]) Contains(v T) bool {
_, found := s[v]
return found
}
func (s Set[T]) ContainsAny(vs []T) bool {
for _, v := range vs {
if _, found := s[v]; found {
return true
}
}
return false
}
func (s Set[T]) Length() int {
return len(s)
}
func (s Set[T]) Stringify() string {
vs := make([]interface{}, len(s))
i := 0
for v := range s {
vs[i] = v
i++
}
return fmt.Sprintf("%v", vs)
}
func main() {
sset := NewSet([]string{"foo", "bar"})
sset.Add("baz")
fmt.Println(sset.Stringify()) // [foo bar baz]
iset := NewSet([]int{12, 13, 14})
iset.Add(20)
fmt.Println(iset.Stringify()) // [12 13 14 20]
}
尤其是:
- 的类型参数中使用的约束
Set
必须是comparable
,因为映射键必须支持比较运算符 (==
,!=
) - 类型参数必须在所有接收器中显式重复,但不需要重复约束。所以你有
func (s Set[T]) ...
所有的方法 - 的实现
Stringify()
很讨厌,因为类型参数T comparable
不支持字符串操作。这只是一个comparable
. 所以上面我天真地使用[]interface{}
andfmt.Sprintf
,它只是完成了这项工作
推荐阅读
- selenium-webdriver - 我需要在 appium-Python 中使用 WebDriverWait,并且需要将“ios_predicate”作为定位器传递
- python - Django静态文件在collectstatic后解析
- javascript - 覆盖 Map.get 方法
- python - Python日志记录:左对齐括号
- php - 如何在 Laravel ORM Eloquent 上转换原始 sql 命令
- kubernetes - 解决入口服务不可用 503
- java - 如何检索现有 Jboss RHQ 服务器上的 SNMP 配置?
- python - 使用 GUI 在 python 中播放 mp4 视频
- sql - 前 5 名的 SQL 并总结“所有其他人”不起作用(“所有其他人”返回 null/0)
- c# - 自动映射器并使用备用键