Golang泛型编程初体验

序言

众所周知,Golang中不支持类似C++/Java中的标记式泛型,所以对于常用算法,比如冒泡排序算法,有些同学容易写出逻辑上重复的代码,即整型是第一套代码,字符串型是第二套代码,用户自定义类型是第三套代码。

重复是万恶之源,我们当然不能容忍,所以要消除重复,使得代码保持在最佳的状态。本文通过一个实际使用的简单算法的演进过程,初次体验了Golang的泛型编程,消除了重复代码,非常自然。

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

今天是星期二,天气晴朗,万里无云,空气清新,我在办公室里听着音乐写着代码,开始了今天的工作。

“小哥,数组切片有没有add和remove函数,可以方便的将元素添加和删除?”

我抬头一看,是小明,就回答道:“什么类型的数组切片?”

小明说:“整型。”

“数组切片中的元素能不能有相同的?”我追问道。

“不能有相同的,我存的都是实体的Id。”小明肯定的回答。

“哦,这个简单,我过会提供一个Slice类,有Add和Remove方法,支持整型。”我有点自信的回答。

小明说完谢谢后,回到了办公位继续工作。

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

type Slice []int

func NewSlice() Slice {
    return make(Slice, 0)
}

func (this* Slice) Add(elem int) error {
    for _, v := range *this {
        if v == elem {
            fmt.Printf("Slice:Add elem: %v already exist\n", elem)
            return ERR_ELEM_EXIST
        }
    }
    *this = append(*this, elem)
    fmt.Printf("Slice:Add elem: %v succ\n", elem)
    return nil
}

func (this* Slice) Remove(elem int) error {
    found := false
    for i, v := range *this {
        if v == elem {
            if i == len(*this) - 1 {
                *this = (*this)[:i]

            } else {
                *this = append((*this)[:i], (*this)[i+1:]...)
            }
            found = true
            break
        }
    }
    if !found {
        fmt.Printf("Slice:Remove elem: %v not exist\n", elem)
        return ERR_ELEM_NT_EXIST
    }
    fmt.Printf("Slice:Remove elem: %v succ\n", elem)
    return nil
}

小明看了我的实现后,说:”我试用一下?"

“丑媳妇不怕见公婆。”我请他试用。

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

func main() {
    intSliceExec()
}

func intSliceExec() {
    fmt.Println("int slice start")
    slice := alg.NewSlice()
    slice.Add(1)
    fmt.Println("current int slice:", slice)
    slice.Add(2)
    fmt.Println("current int slice:", slice)
    slice.Add(2)
    fmt.Println("current int slice:", slice)
    slice.Add(3)
    fmt.Println("current int slice:", slice)
    slice.Remove(2)
    fmt.Println("current int slice:", slice)
    slice.Remove(2)
    fmt.Println("current int slice:", slice)
    slice.Remove(3)
    fmt.Println("current int slice:", slice)
    fmt.Println("int slice end")
}

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

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

int slice start
Slice:Add elem: 1 succ
current int slice: [1]
Slice:Add elem: 2 succ
current int slice: [1 2]
Slice:Add elem: 2 already exist
current int slice: [1 2]
Slice:Add elem: 3 succ
current int slice: [1 2 3]
Slice:Remove elem: 2 succ
current int slice: [1 3]
Slice:Remove elem: 2 not exist
current int slice: [1 3]
Slice:Remove elem: 3 succ
current int slice: [1]
int slice end

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

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

周三下午,睡完午觉后精神有点小抖擞,浏览者邮件,突然发现公司又接了一个大单,于是吃了会精神食量。

”小哥,小哥!“

我抬头一看,是小雷。

”咋的啦,哥们?“我好奇的问道。

”听说你昨天实现了一个数组切片算法,已支持整型,我现在想用字符串型的数组切片算法,你能提供不?"小雷有点着急的问道。

我心里一想,Golang支持Any类型,即interface{},同时字符串和整型一样都可以直接用”==“运算符比较两个元素是否相等,所以你懂的。

”这个好实现,给我一首歌的时间就可以试用。“我说完后,就立刻修改起了代码。

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

type Slice []interface{}

func NewSlice() Slice {
    return make(Slice, 0)
}

func (this* Slice) Add(elem interface{}) error {
    for _, v := range *this {
        if v == elem {
            fmt.Printf("Slice:Add elem: %v already exist\n", elem)
            return ERR_ELEM_EXIST
        }
    }
    *this = append(*this, elem)
    fmt.Printf("Slice:Add elem: %v succ\n", elem)
    return nil
}

func (this* Slice) Remove(elem interface{}) error {
    found := false
    for i, v := range *this {
        if v == elem {
            if i == len(*this) - 1 {
                *this = (*this)[:i]

            } else {
                *this = append((*this)[:i], (*this)[i+1:]...)
            }
            found = true
            break
        }
    }
    if !found {
        fmt.Printf("Slice:Remove elem: %v not exist\n", elem)
        return ERR_ELEM_NT_EXIST
    }
    fmt.Printf("Slice:Remove elem: %v succ\n", elem)
    return nil
}

不难发现,改动很简单,只将三个地方的int改成了interface{},一切都是这么自然。

”哇塞,这么快?半首歌我还没听完。“小雷开森的说。

”简单设计,呵呵!“我们不约而同的说出了这个大家最爱说又最难做到的XP实践。

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

func main() {
    intSliceExec()
    fmt.Println("")
    stringSliceExec()
}

func stringSliceExec() {
    fmt.Println("string slice start")
    slice := alg.NewSlice()
    slice.Add("hello")
    fmt.Println("current string slice:", slice)
    slice.Add("golang")
    fmt.Println("current string slice:", slice)
    slice.Add("golang")
    fmt.Println("current string slice:", slice)
    slice.Add("generic")
    fmt.Println("current string slice:", slice)
    slice.Remove("golang")
    fmt.Println("current string slice:", slice)
    slice.Remove("golang")
    fmt.Println("current string slice:", slice)
    slice.Remove("generic")
    fmt.Println("current string slice:", slice)
    fmt.Println("string slice end")
}
...

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

int slice start
Slice:Add elem: 1 succ
current int slice: [1]
Slice:Add elem: 2 succ
current int slice: [1 2]
Slice:Add elem: 2 already exist
current int slice: [1 2]
Slice:Add elem: 3 succ
current int slice: [1 2 3]
Slice:Remove elem: 2 succ
current int slice: [1 3]
Slice:Remove elem: 2 not exist
current int slice: [1 3]
Slice:Remove elem: 3 succ
current int slice: [1]
int slice end

string slice start
Slice:Add elem: hello succ
current string slice: [hello]
Slice:Add elem: golang succ
current string slice: [hello golang]
Slice:Add elem: golang already exist
current string slice: [hello golang]
Slice:Add elem: generic succ
current string slice: [hello golang generic]
Slice:Remove elem: golang succ
current string slice: [hello generic]
Slice:Remove elem: golang not exist
current string slice: [hello generic]
Slice:Remove elem: generic succ
current string slice: [hello]
string slice end

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

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

今天周四,眼看明天就周五了,打算中午出去吃个自助餐提高一下生活质量,于是叫着小方开着车就杀出去了。由于在一点半之前要回到公司上班,所以匆匆地找了一家自助餐店。

“哇靠,人真多!”小方这样感叹道。

“这个店应该搞成多种模式,比如选取大家常吃的几种套餐(A,C,D),这样百分之七十的上班族都会直接领套餐,就不会白白浪费排队时间了。”我不着边际的边想边说。

“自助餐还是更有吸引力,顾客可以任意搭配,做到真正的私人订制,而套餐吃几次就腻味了。”小方反驳着对我说。

...

紧赶慢赶,终于,终于在一点半前回到了公司,于是又开始编码了。

“小哥,听说你实现了一个数组切片算法,既支持整型,又支持字符串型,我这还有一个小小需求。”

我抬头一看,是小方,就问“啥子需求?“

”我这边有自定义的struct类型,也想用数组切片算法。“小方大方的提出需求。

”这个嘛,这个嘛,有点难度!“我边思考边回应:”给我半个小时,让我试试。“

”好的,小哥。“小方说完后露出了惬意的笑。

我们先自定义一个类型:

type Student struct {
    id string
    name string
}

Student类型有两个数据成员,即id和name。id是学号,全局我唯一;name是中文名字的拼音,可重复。

用户自定义类型和基本类型(int或string)不同的是两个元素是否相等的判断方式不一样:

1.基本类型(int或string)直接通过”==“运算符来判断;

2.用户自定义类型万千种种,数组切片算法中不可能知道,所以需要通过interface提供的方法进行两个元素是否相等的判断。

我们接着定义一个interface:

type Comparable interface {
    IsEqual(obj interface{}) bool
}

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

func (this Student) IsEqual(obj interface{}) bool {
    if student, ok := obj.(Student); ok {
        return this.GetId() == student.GetId()
    }
    panic("unexpected type")
}

func (this Student) GetId() string {
    return this.id
}

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

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

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

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

func (this* Slice) Add(elem interface{}) error {
    for _, v := range *this {
        if isEqual(v, elem) {
            fmt.Printf("Slice:Add elem: %v already exist\n", elem)
            return ERR_ELEM_EXIST
        }
    }
    *this = append(*this, elem)
    fmt.Printf("Slice:Add elem: %v succ\n", elem)
    return nil
}

func (this* Slice) Remove(elem interface{}) error {
    found := false
    for i, v := range *this {
        if isEqual(v, elem) {
            if i == len(*this) - 1 {
                *this = (*this)[:i]

            } else {
                *this = append((*this)[:i], (*this)[i+1:]...)
            }
            found = true
            break
        }
    }
    if !found {
        fmt.Printf("Slice:Remove elem: %v not exist\n", elem)
        return ERR_ELEM_NT_EXIST
    }
    fmt.Printf("Slice:Remove elem: %v succ\n", elem)
    return nil
}

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

func isEqual(a, b interface{}) bool {
    if comparable, ok := a.(Comparable); ok {
        return comparable.IsEqual(b)
    } else {
        return a == b
    }
}

半个小时后,我完成了代码,叫小方过来试用。

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

func main() {
    intSliceExec()
    fmt.Println("")
    stringSliceExec()
    fmt.Println("")
    structSliceExec()
}

func structSliceExec() {
    fmt.Println("struct slice start")
    xiaoMing := Student{"1001", "xiao ming"}
    xiaoLei := Student{"1002", "xiao lei"}
    xiaoFang := Student{"1003", "xiao fang"}
    slice := alg.NewSlice()
    slice.Add(xiaoMing)
    fmt.Println("current struct slice:", slice)
    slice.Add(xiaoLei)
    fmt.Println("current struct slice:", slice)
    slice.Add(xiaoLei)
    fmt.Println("current struct slice:", slice)
    slice.Add(xiaoFang)
    fmt.Println("current struct slice:", slice)
    slice.Remove(xiaoLei)
    fmt.Println("current struct slice:", slice)
    slice.Remove(xiaoLei)
    fmt.Println("current struct slice:", slice)
    slice.Remove(xiaoFang)
    fmt.Println("current struct slice:", slice)
    fmt.Println("struct slice end")
}
...

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

int slice start
Slice:Add elem: 1 succ
current int slice: [1]
Slice:Add elem: 2 succ
current int slice: [1 2]
Slice:Add elem: 2 already exist
current int slice: [1 2]
Slice:Add elem: 3 succ
current int slice: [1 2 3]
Slice:Remove elem: 2 succ
current int slice: [1 3]
Slice:Remove elem: 2 not exist
current int slice: [1 3]
Slice:Remove elem: 3 succ
current int slice: [1]
int slice end

string slice start
Slice:Add elem: hello succ
current string slice: [hello]
Slice:Add elem: golang succ
current string slice: [hello golang]
Slice:Add elem: golang already exist
current string slice: [hello golang]
Slice:Add elem: generic succ
current string slice: [hello golang generic]
Slice:Remove elem: golang succ
current string slice: [hello generic]
Slice:Remove elem: golang not exist
current string slice: [hello generic]
Slice:Remove elem: generic succ
current string slice: [hello]
string slice end

struct slice start
Slice:Add elem: {1001 xiao ming} succ
current struct slice: [{1001 xiao ming}]
Slice:Add elem: {1002 xiao lei} succ
current struct slice: [{1001 xiao ming} {1002 xiao lei}]
Slice:Add elem: {1002 xiao lei} already exist
current struct slice: [{1001 xiao ming} {1002 xiao lei}]
Slice:Add elem: {1003 xiao fang} succ
current struct slice: [{1001 xiao ming} {1002 xiao lei} {1003 xiao fang}]
Slice:Remove elem: {1002 xiao lei} succ
current struct slice: [{1001 xiao ming} {1003 xiao fang}]
Slice:Remove elem: {1002 xiao lei} not exist
current struct slice: [{1001 xiao ming} {1003 xiao fang}]
Slice:Remove elem: {1003 xiao fang} succ
current struct slice: [{1001 xiao ming}]
struct slice end

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

小结

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

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2017-12-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏禁心尽力

Java设计模式之模板方法设计模式(银行计息案例)

       不知道为什么,这几天对Java中的设计模式非常感兴趣,恰巧呢这几天公司的开发任务还不算太多,趁着有时间昨天又把模板方法模式深入学习了一下,做了一个...

35980
来自专栏前端小栈

javaScript循环总结(for,for-in,for-of,forEach,map,filter,every,reduce,reduceRight)

循环是每个语言都必不可少的方法,javaScript也一样,随着javaScript的发展,我们用于循环的方法也在不断改进,也越来越精简,但是用好循环却不是那...

10830
来自专栏菜鸟前端工程师

JavaScript学习笔记018-面向对象编程思维0构造函数0Class

11320
来自专栏CSDN技术头条

为什么我坚持使用 JavaScript 函数声明

时光溯回到上世纪 90 年代晚期,在初次接触 JavaScript 时,老师教我们使用函数声明写下Hello World,它看上去是这样的······ fun...

22580
来自专栏Jed的技术阶梯

Java设计模式之工厂方法模式

女娲补天的故事大家都听说过吧,今天不说这个,说女娲创造人的故事,可不是“造人”的工作,这个词被现代人滥用了。这个故事是说,女娲在补了天后,下到凡间一看,哇塞,风...

40120
来自专栏葬爱家族

Android高德之旅(14)行政区划搜索废话简介总结

前后两千万,拍照更清晰。大家好,这里是OPPO R11独家冠名赞助播出的大型情感类电视连续剧《Android高德之旅》,我是主持人大公爵。(开篇占位)

19610
来自专栏菩提树下的杨过

Flash/Flex学习笔记(44):万有引力与粒子系统

万有引用公式: ? 其中G为万有引力常数 var numParticles:uint=50;//粒子总数 var G:Number=0.03;//万有引力常数 ...

299100
来自专栏Coding迪斯尼

面试算法:二分查找法寻找数组截断点

12420
来自专栏HansBug's Lab

从Hash Killer I、II、III论字符串哈希

首先,Hash Killer I、II、III是BZOJ上面三道很经典的字符串哈希破解题。当时关于II,本人还琢磨了好久,但一直不明白为啥别人AC的代码都才0....

36340
来自专栏web前端教室

=>,Es6箭头符号的前世今生

看代码能看错行,一看书就头疼且双眼流泪,没办法啊,都是娘胎里带出来的毛病,能看点是点吧。es6这种东西,虽然有一些语法糖,但毕竟也是新东西啊,得学。不学就落后,...

249100

扫码关注云+社区

领取腾讯云代金券