Golang泛型编程初体验

序言

众所周知,Golang中不支持类似C++/Java中的标记式泛型,所以对于常用算法,比如冒泡排序算法,有些同学容易写出逻辑上重复的代码,即整型是第一套代码,字符串型是第二套代码,用户自定义类型是第三套代码。
重复是万恶之源,我们当然不能容忍,所以要消除重复,使得代码保持在最佳的状态。本文通过一个实际使用的简单算法的演进过程,初次体验了Golang的泛型编程,消除了重复代码,非常自然。

需求一:切片算法支持整型

今天是星期二,天气晴朗,万里无云,空气清新,我在办公室里听着音乐写着代码,开始了今天的工作。
“小哥,数组切片有没有add和remove函数,可以方便的将元素添加和删除?”
我抬头一看,是小明,就回答道:“什么类型的数组切片?”
小明说:“整型。”
”数组切片中的元素能不能有相同的?“我追问道。
”不能有相同的,我存的都是实体的Id。“小明肯定的回答。
“哦,这个简单,我过会提供一个Slice类,有Add和Remove方法,支持整型。”我有点自信的回答。
小明说完谢谢后,回到了办公位继续工作。

一个小时后,我写完了支持整型的切片算法:

1type Slice []int 2 3func NewSlice() Slice { 4 return make(Slice, 0) 5} 6 7func (this* Slice) Add(elem int) error { 8 for _, v := range *this { 9 if v == elem { 10 fmt.Printf("Slice:Add elem: %v already exist\n", elem) 11 return ERR_ELEM_EXIST 12 } 13 } 14 *this = append(*this, elem) 15 fmt.Printf("Slice:Add elem: %v succ\n", elem) 16 return nil 17} 18 19func (this* Slice) Remove(elem int) error { 20 found := false 21 for i, v := range *this { 22 if v == elem { 23 if i == len(*this) - 1 { 24 *this = (*this)[:i] 25 26 } else { 27 *this = append((*this)[:i], (*this)[i+1:]...) 28 } 29 found = true 30 break 31 } 32 } 33 if !found { 34 fmt.Printf("Slice:Remove elem: %v not exist\n", elem) 35 return ERR_ELEM_NT_EXIST 36 } 37 fmt.Printf("Slice:Remove elem: %v succ\n", elem) 38 return nil 39}

小明看了我的实现后,说:”我试用一下?"
“丑媳妇不怕见公婆。“我请他试用。

小明用了5分钟,写了下面的代码:

1func main() { 2 intSliceExec() 3} 4 5func intSliceExec() { 6 fmt.Println("int slice start") 7 slice := alg.NewSlice() 8 slice.Add(1) 9 fmt.Println("current int slice:", slice) 10 slice.Add(2) 11 fmt.Println("current int slice:", slice) 12 slice.Add(2) 13 fmt.Println("current int slice:", slice) 14 slice.Add(3) 15 fmt.Println("current int slice:", slice) 16 slice.Remove(2) 17 fmt.Println("current int slice:", slice) 18 slice.Remove(2) 19 fmt.Println("current int slice:", slice) 20 slice.Remove(3) 21 fmt.Println("current int slice:", slice) 22 fmt.Println("int slice end") 23}

从试用代码中可以看出,整型数组切片中最多有三个元素[1 2 3],元素2插入的第二次应该失败,同理元素2删除的第二次也应该失败,整型数组切片最后只剩下一个元素[1]。

go run运行代码后,日志如下:

1int slice start 2Slice:Add elem: 1 succ 3current int slice: [1] 4Slice:Add elem: 2 succ 5current int slice: [1 2] 6Slice:Add elem: 2 already exist 7current int slice: [1 2] 8Slice:Add elem: 3 succ 9current int slice: [1 2 3] 10Slice:Remove elem: 2 succ 11current int slice: [1 3] 12Slice:Remove elem: 2 not exist 13current int slice: [1 3] 14Slice:Remove elem: 3 succ 15current int slice: [1] 16int slice end

查看日志,结果符合期望。

需求二:切片算法支持字符串

周三下午,睡完午觉后精神有点小抖擞,浏览者邮件,突然发现公司又接了一个大单,于是吃了会精神食量。
”小哥,小哥!“
我抬头一看,是小雷。
”咋的啦,哥们?“我好奇的问道。
”听说你昨天实现了一个数组切片算法,已支持整型,我现在想用字符串型的数组切片算法,你能提供不?"小雷有点着急的问道。
我心里一想,Golang支持Any类型,即interface{},同时字符串和整型一样都可以直接用”==“运算符比较两个元素是否相等,所以你懂的。
”这个好实现,给我一首歌的时间就可以试用。“我说完后,就立刻修改起了代码。

两分钟后,我提供了新版本的代码:

1type Slice []interface{} 2 3func NewSlice() Slice { 4 return make(Slice, 0) 5} 6 7func (this* Slice) Add(elem interface{}) error { 8 for _, v := range *this { 9 if v == elem { 10 fmt.Printf("Slice:Add elem: %v already exist\n", elem) 11 return ERR_ELEM_EXIST 12 } 13 } 14 *this = append(*this, elem) 15 fmt.Printf("Slice:Add elem: %v succ\n", elem) 16 return nil 17} 18 19func (this* Slice) Remove(elem interface{}) error { 20 found := false 21 for i, v := range *this { 22 if v == elem { 23 if i == len(*this) - 1 { 24 *this = (*this)[:i] 25 26 } else { 27 *this = append((*this)[:i], (*this)[i+1:]...) 28 } 29 found = true 30 break 31 } 32 } 33 if !found { 34 fmt.Printf("Slice:Remove elem: %v not exist\n", elem) 35 return ERR_ELEM_NT_EXIST 36 } 37 fmt.Printf("Slice:Remove elem: %v succ\n", elem) 38 return nil 39}

不难发现,改动很简单,只将三个地方的int改成了interface{},一切都是这么自然。
”哇塞,这么快?半首歌我还没听完。“小雷开森的说。
”简单设计,呵呵!“我们不约而同的说出了这个大家最爱说又最难做到的XP实践。

这次有了拷贝这个强大的武器,小雷两分钟就写完了试用代码:

1func main() { 2 intSliceExec() 3 fmt.Println("") 4 stringSliceExec() 5} 6 7func stringSliceExec() { 8 fmt.Println("string slice start") 9 slice := alg.NewSlice() 10 slice.Add("hello") 11 fmt.Println("current string slice:", slice) 12 slice.Add("golang") 13 fmt.Println("current string slice:", slice) 14 slice.Add("golang") 15 fmt.Println("current string slice:", slice) 16 slice.Add("generic") 17 fmt.Println("current string slice:", slice) 18 slice.Remove("golang") 19 fmt.Println("current string slice:", slice) 20 slice.Remove("golang") 21 fmt.Println("current string slice:", slice) 22 slice.Remove("generic") 23 fmt.Println("current string slice:", slice) 24 fmt.Println("string slice end") 25} 26...

从试用代码中可以看出,字符串型数组切片中最多有三个元素[hello golang generic],元素golang插入的第二次应该失败,同理元素golang删除的第二次也应该失败,字符串型数组切片最后只剩下一个元素[hello]。

1int slice start 2Slice:Add elem: 1 succ 3current int slice: [1] 4Slice:Add elem: 2 succ 5current int slice: [1 2] 6Slice:Add elem: 2 already exist 7current int slice: [1 2] 8Slice:Add elem: 3 succ 9current int slice: [1 2 3] 10Slice:Remove elem: 2 succ 11current int slice: [1 3] 12Slice:Remove elem: 2 not exist 13current int slice: [1 3] 14Slice:Remove elem: 3 succ 15current int slice: [1] 16int slice end 17 18string slice start 19Slice:Add elem: hello succ 20current string slice: [hello] 21Slice:Add elem: golang succ 22current string slice: [hello golang] 23Slice:Add elem: golang already exist 24current string slice: [hello golang] 25Slice:Add elem: generic succ 26current string slice: [hello golang generic] 27Slice:Remove elem: golang succ 28current string slice: [hello generic] 29Slice:Remove elem: golang not exist 30current string slice: [hello generic] 31Slice:Remove elem: generic succ 32current string slice: [hello] 33string slice end

查看日志,结果符合期望。

需求三:切片算法支持用户自定义的类型

今天周四,眼看明天就周五了,打算中午出去吃个自助餐提高一下生活质量,于是叫着小方开着车就杀出去了。由于在一点半之前要回到公司上班,所以匆匆地找了一家自助餐店。
“哇靠,人真多!”小方这样感叹道。
“这个店应该搞成多种模式,比如选取大家常吃的几种套餐(A,C,D),这样百分之七十的上班族都会直接领套餐,就不会白白浪费排队时间了。”我不着边际的边想边说。
“自助餐还是更有吸引力,顾客可以任意搭配,做到真正的私人订制,而套餐吃几次就腻味了。”小方反驳着对我说。
...
紧赶慢赶,终于,终于在一点半前回到了公司,于是又开始编码了。

“小哥,听说你实现了一个数组切片算法,既支持整型,又支持字符串型,我这还有一个小小需求。”
我抬头一看,是小方,就问“啥子需求?“
”我这边有自定义的struct类型,也想用数组切片算法。“小方大方的提出需求。
”这个嘛,这个嘛,有点难度!“我边思考边回应:”给我半个小时,让我试试。“
”好的,小哥。“小方说完后露出了惬意的笑。

我们先自定义一个类型:

1type Student struct { 2 id string 3 name string 4}

Student类型有两个数据成员,即id和name。id是学号,全局我唯一;name是中文名字的拼音,可重复。
用户自定义类型和基本类型(int或string)不同的是两个元素是否相等的判断方式不一样:

  1. 基本类型(int或string)直接通过”==“运算符来判断;
  2. 用户自定义类型万千种种,数组切片算法中不可能知道,所以需要通过interface提供的方法进行两个元素是否相等的判断。

我们接着定义一个interface:

1type Comparable interface { 2 IsEqual(obj interface{}) bool 3}

只要用户自定义的类型实现了接口Comparable,就可以调用它的方法IsEqual进行两个元素是否相等的判断了,于是我们实现了Student类型的IsEqual方法:

1func (this Student) IsEqual(obj interface{}) bool { 2 if student, ok := obj.(Student); ok { 3 return this.GetId() == student.GetId() 4 } 5 panic("unexpected type") 6} 7 8func (this Student) GetId() string { 9 return this.id 10}

用户自定义的GetId方法是必要的,因为Id不一定就是数据成员,可能是由多个数据成员拼接而成。

我们将数组切片算法的易变部分”v == elem"抽出来封装成方法:

1func isEqual(a, b interface{}) bool { 2 return a == b 3}

于是数组切片的Add方法和Remove方法就变成:

1func (this* Slice) Add(elem interface{}) error { 2 for _, v := range *this { 3 if isEqual(v, elem) { 4 fmt.Printf("Slice:Add elem: %v already exist\n", elem) 5 return ERR_ELEM_EXIST 6 } 7 } 8 *this = append(*this, elem) 9 fmt.Printf("Slice:Add elem: %v succ\n", elem) 10 return nil 11} 12 13func (this* Slice) Remove(elem interface{}) error { 14 found := false 15 for i, v := range *this { 16 if isEqual(v, elem) { 17 if i == len(*this) - 1 { 18 *this = (*this)[:i] 19 20 } else { 21 *this = append((*this)[:i], (*this)[i+1:]...) 22 } 23 found = true 24 break 25 } 26 } 27 if !found { 28 fmt.Printf("Slice:Remove elem: %v not exist\n", elem) 29 return ERR_ELEM_NT_EXIST 30 } 31 fmt.Printf("Slice:Remove elem: %v succ\n", elem) 32 return nil 33}

于是数组切片算法对于支持用户自定义类型的改动仅仅局限于isEqual函数了,我们通过接口查询来完成代码修改:

1func isEqual(a, b interface{}) bool { 2 if comparable, ok := a.(Comparable); ok { 3 return comparable.IsEqual(b) 4 } else { 5 return a == b 6 } 7}

半个小时后,我完成了代码,叫小方过来试用。
因为有拷贝这个强大的武器,小雷三分钟就写完了试用代码:

1func main() { 2 intSliceExec() 3 fmt.Println("") 4 stringSliceExec() 5 fmt.Println("") 6 structSliceExec() 7} 8 9func structSliceExec() { 10 fmt.Println("struct slice start") 11 xiaoMing := Student{"1001", "xiao ming"} 12 xiaoLei := Student{"1002", "xiao lei"} 13 xiaoFang := Student{"1003", "xiao fang"} 14 slice := alg.NewSlice() 15 slice.Add(xiaoMing) 16 fmt.Println("current struct slice:", slice) 17 slice.Add(xiaoLei) 18 fmt.Println("current struct slice:", slice) 19 slice.Add(xiaoLei) 20 fmt.Println("current struct slice:", slice) 21 slice.Add(xiaoFang) 22 fmt.Println("current struct slice:", slice) 23 slice.Remove(xiaoLei) 24 fmt.Println("current struct slice:", slice) 25 slice.Remove(xiaoLei) 26 fmt.Println("current struct slice:", slice) 27 slice.Remove(xiaoFang) 28 fmt.Println("current struct slice:", slice) 29 fmt.Println("struct slice end") 30} 31...

从试用代码中可以看出,用户自定义类型的数组切片中最多有三个元素[{1001 xiao ming} {1002 xiao lei} {1003 xiao fang}],元素{1002 xiao lei}插入的第二次应该失败,同理元素{1002 xiao lei}删除的第二次也应该失败,用户自定义类型的数组切片最后只剩下一个元素[{1001 xiao ming}]。

1int slice start 2Slice:Add elem: 1 succ 3current int slice: [1] 4Slice:Add elem: 2 succ 5current int slice: [1 2] 6Slice:Add elem: 2 already exist 7current int slice: [1 2] 8Slice:Add elem: 3 succ 9current int slice: [1 2 3] 10Slice:Remove elem: 2 succ 11current int slice: [1 3] 12Slice:Remove elem: 2 not exist 13current int slice: [1 3] 14Slice:Remove elem: 3 succ 15current int slice: [1] 16int slice end 17 18string slice start 19Slice:Add elem: hello succ 20current string slice: [hello] 21Slice:Add elem: golang succ 22current string slice: [hello golang] 23Slice:Add elem: golang already exist 24current string slice: [hello golang] 25Slice:Add elem: generic succ 26current string slice: [hello golang generic] 27Slice:Remove elem: golang succ 28current string slice: [hello generic] 29Slice:Remove elem: golang not exist 30current string slice: [hello generic] 31Slice:Remove elem: generic succ 32current string slice: [hello] 33string slice end 34 35struct slice start 36Slice:Add elem: {1001 xiao ming} succ 37current struct slice: [{1001 xiao ming}] 38Slice:Add elem: {1002 xiao lei} succ 39current struct slice: [{1001 xiao ming} {1002 xiao lei}] 40Slice:Add elem: {1002 xiao lei} already exist 41current struct slice: [{1001 xiao ming} {1002 xiao lei}] 42Slice:Add elem: {1003 xiao fang} succ 43current struct slice: [{1001 xiao ming} {1002 xiao lei} {1003 xiao fang}] 44Slice:Remove elem: {1002 xiao lei} succ 45current struct slice: [{1001 xiao ming} {1003 xiao fang}] 46Slice:Remove elem: {1002 xiao lei} not exist 47current struct slice: [{1001 xiao ming} {1003 xiao fang}] 48Slice:Remove elem: {1003 xiao fang} succ 49current struct slice: [{1001 xiao ming}] 50struct slice end

查看日志,结果符合期望。

小结

本文通过一种轻松愉快的方式阐述了实际使用的数组切片算法的演进过程,同时也是笔者使用Golang进行泛型编程的第一次旅行,再次领略了Golang中interface的强大魅力,希望对读者也有一定的启发。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )