GO语言的进阶之路-面向对象编程

Posted 尹正杰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GO语言的进阶之路-面向对象编程相关的知识,希望对你有一定的参考价值。

                                                                  GO语言的进阶之路-面向对象编程

                                                作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

 

  当你看完这篇文章之时,我可以说你的Golang算是入门了,何为入门?就是你去看Docker 源码能看懂60%的语法结构,因为涉及一些unix的代码可能没有Linux运维基础的同学在学习的时候会很吃力,看起来也会带来一定的难度,如果有时间的话我会给大家解析Docker部门精辟的源码。好了,回归正题吧,我们今天要学习的内容是什么呢?即面向对象编程。当然,不要用屌丝的心态来说:“那要是没对象的还咋编程呢?”,哈哈~正杰要告诉你的是:“此对象非彼对象”,没有“对象”照样编程啊!那么问题来了,到底什么事面向对象编程呢?

 

一.什么是面向对象编程;

   在Golang的对象可以用一句话总结:“面向对象就是将要处理的数据跟函数进行绑定的方法”。

  如果你从来没有学过Python的话就是可以理解是class,如果在学习Golang之前从来没有接触过其他语言(比如说我)的话,那么你可以这样理解:“它是一种编程风格,就是把一切东西看成一个个对象,比如人,车,面包,等等,然后把这些对象拥有的属性变量,比如年龄,民族,工作地点,变质期,寿命,还有操作这些属性变量的函数打包成一个类来表示,这个类的一个抽象就是一个对象,比如人这个类包含一些属性,比如年龄,名字,住址等,他还有一些对别人告诉这些属性的功能,比如:说,看,走等!!”。这就是的面向对象的特点!!!

 

二.为什么要有面向对象编程;

  说到面向对象编程,就不得不说一下面向过程编程,我上次跟大家分享过面向过程编程的方法,也就是定义一些函数,减少了代码的重复性,增加了代码的扩展性和易读性等等。而且当大家“啪啪啪”代码敲的起劲的时候突然冒出个面向对象,对它的出现不免会有所疑惑,面向过程已经如此好了,干嘛还要面向对象呢?其实,我们举个例子来说明一下你就知道了。比如让你写一个人的模型,用函数写你要如果实现呢?比如现在要让你写关于:“刘德华,范冰冰,蔡依林”他们三个人的特点,没错,你可以面向过程式编程用函数将他们的特点定义 出来,那么问题来了,如果我想在外部调用“刘德华”或是“范冰冰”的特点该如果还实现呢?用函数可能非常难以实现。因为面向过程式编程虽然不印象实现的功能,但是其复杂度很低,所以,在实现一些功能上可能欠缺点火候。
  这个时候,面向对象就思想就出来啦,面向对象就可能很轻松的实现在外部调用“刘德华”或是“范冰冰”的特点。想要了解更多关于面向对象的发展是可以问我的大师兄“百度”,当然也可以问下我的二师兄“谷歌”,在这里不扯太多历史了,大家多去敲一些代码就会体现面向对象的好处,因为Go是一个完全面向对象的语言。例如,它允许基于我们定义的类型的方法,而没有像其他语言一样的装箱/拆箱操作。其实
接触面向对象编程起初大家都是拒绝的,都有抵触心理。但是时间一长你就知道哪个才是你想要的!就好像你整天和你的男性朋友玩的很愉快,突然有一天一个长得非常哇塞的小姐姐出现在你面前,你在你的男性朋友面向说着:“这女孩也就那样,长得还行吧”,然后备注兄弟私下找机会和这个妹子约会,吃饭,看电影是一个道理。

 

三.如果定义一个对象;

  当你看到这里的时候,恭喜你成功被洗脑了,如果你现在还有抵触心理学习面向对象编程的话,建议关闭该网页,因为内心的抵触你在学习这篇博客的内容会很吃力,可能看着看着你就不懂了,忘记之前的面向过程编程,让我们重新学习一种新的编程风格吧。当然,我们也可以对比一下两者的不同,这样学习起来也方便记忆。

  下面的案例是:在二维空间中,求两点之间的距离。

1.用函数实现求两点的距离;

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "math"
12     "fmt"
13 )
14 
15 type Point struct {
16     X,Y float64
17 }
18 
19 func Distence(p, q Point) float64 {
20     return math.Hypot(q.X-p.X,q.Y-p.Y)  //"Hypot"是计算两点之间点距离
21 }
22 
23 func main() {
24     p := Point{1,2}
25     q := Point{4,6}
26     fmt.Println(Distence(p,q)) //函数的调用方式,即传值的方式调用。
27 }
28 
29 
30 
31 #以上代码输出结果如下:
32 5

2.用对象实现求两点的距离;

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "math"
12     "fmt"
13 )
14 
15 type Point struct { //定义一个结构题体,你可以理解是是Python中的class
16     X,Y float64
17 }
18 
19 func (p Point)Distence(q Point) float64 { //给p对象定义一个Distence的方法,你可以理解绑定了一个Distence的方法。
20     return math.Hypot(q.X-p.X,q.Y-p.Y)
21 }
22 
23 func main() {
24     p := Point{1,2}
25     q := Point{4,6}
26     fmt.Println((p.Distence(q))) //类的调用方式,注意,如果定义就要如何调用!(这里是调用p的Distence方法。)
27 }
28 
29 
30 
31 #以上代码输出结果如下:
32 5

   当你看了这两段代码你现在可能会反问,实现的效果都是一样的啊,即他们的运行结果都相同,只是在定义和调用的方式不同而已。并不能明显体现出他们的差异性。也比较不出来他们的差别。我只是想说好戏还在后头,我们慢慢来体会它的奥妙之处,现在,我们就知道如何定义了一个对象了吧。那求多个点的长度又该如果定义呢?

3.小试牛刀;

 计算多个点的连线的总长度,实现代码如下:

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "math"
12     "fmt"
13 )
14 
15 type Point struct {
16     X,Y float64
17 }
18 
19 
20 func (p Point)Distence(q Point) float64 {
21     return math.Hypot(q.X-p.X,q.Y-p.Y)
22 }
23 
24 func Distence(path []Point) float64 { //定义一个path变了,path其是包含Point类型的数组切片, Slice可以理解为动态增长的数组.
25     var   s float64
26     for i := 0;i <len(path) - 1 ; i++ {
27         s += path[i].Distence(path[i+1])
28     }
29     return s
30 }
31 
32 func main() {
33     path := []Point{{10,20},{30,40},{50,60}}
34     fmt.Println(Distence(path))
35 }
36 
37 
38 
39 #以上代码输出结果如下:
40 56.568542494923804

 

四.给对象定义一个别名;

  玩过linux的朋友可能知道:“alias”这个命令,没错,就是起别名的意思,目的是为了方便用户操作,在Golang里也有可以用type设置别名,这种方法在Go里面随处可见,因为Golang就是一门面向对象编程的语言。

1.Time模块的使用;

  也许,您在看官网的时候,会发现time模块,起本质的实现就是基于type的起别名的方法来实现新的功能,将原本的“float64”起别名为“Duration”类型,最后给“Duration”类型绑定特有的方法,这样time也就有了很多中方法,比如Now,String,Second等方法,下面我们来看看time模块常用的方法。

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "time"
12     "fmt"
13 )
14 
15 func main() {
16     var   n time.Duration //其实"Duration"就是利用别名来实现的。
17     n = 3 * time.Hour + 30 * time.Second  //表示3小时又30分钟的时间。
18     fmt.Println(int64(n))
19     fmt.Println(n.String()) //可读性最高,这是"Duration"特有的方法。
20     fmt.Println(n.Seconds())
21     fmt.Println(n.Minutes())
22 
23 
24     t := time.Now()
25 
26     t1 := t.Add(time.Hour)
27     t2 := t.Add(-time.Hour)
28 
29     fmt.Println(t1)
30     fmt.Println(t2)
31     fmt.Println(t1.Sub(t2)) //计算时间长度
32 }
33 
34 
35 
36 #以上代码输出结果如下:
37 10830000000000
38 3h0m30s
39 10830
40 180.5
41 2017-07-09 10:49:45.8808815 +0800 CST
42 2017-07-09 08:49:45.8808815 +0800 CST
43 2h0m0s

 

2.定义一个新类型;

  由于Golang的默认编码是中文编码,所以我们才存变了的时候可以用中文来当做变量名,在python3.x版本默认的编码也是utf-8,这一点大家很喜欢。但是我要在这里说的是,可以这么干,但是最好不要这么干,因为在涉及到对象的属性的时候你会遇到一些坑,先不要着急,我在后面的博客中也会分享到关于Golang的公有属性和私有属性。大家可以一起看看我用中文定义的变量名称,大家不要这么干,我这里就是为了方便说明如何定义对象,如果给定义的对象起别名,以及如何调用对象等等。

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import "fmt"
11 
12 type 车的属性 struct {
13     名称 string
14     描述 string
15     是否需要备用 bool
16 }
17 
18 type 车的特性 []车的属性 //“车的特性”类型是包含结构体“车的属性”类型的数组切片,你可以理解是起了一个别名。
19 
20 func (零件 车的特性) 备件方法()(车的信息 车的特性)  {  //给“车的特性”绑定一个叫“备件”的方法起名为“零件”。
21     for _,part := range 零件{
22         if part.是否需要备用 { //只将有备胎的车追加到“车的信息”这个空切片中。
23             车的信息 = append(车的信息,part)
24         }
25     }
26     return 车的信息
27 }
28 
29 type 汽车 struct { //“汽车”由“车身大小”组成
30     车身大小 string
31     车的特性 //没有给“车的特性”指定一个名称,我们是要保证实现“内嵌”。这样可以提供自动的委托,不需特殊的声明,
32     // 例如“汽车.备件方法()”和“汽车.车的特性.备件方法()”是等同的。
33 }
34 
35 
36 var (
37     特斯拉 = 车的特性{
38         {"Tesla_90D(加速时间)", "100km/2.9s", true},
39         {"车身大小", "109.47万元", false},
40         {"颜色", "red", false},
41     }
42 
43     宝马 = 车的特性{
44         {"BMW M4敞篷轿跑车(加速时间)", "100km/4.4s", true},
45         {"价格", "1,098,000美元", true},
46         {"倍耐力轮胎", "兰博基尼Huracan LP580-2前轮原配", true},
47         {"夏季冰丝汽车坐垫", "1088.00", true},
48     }
49 
50     兰博基尼 = 车的特性{
51         {"Avetador(加速时间)", "100km/2.8s", true},
52         {"价格", "648.80-801.15万", true},
53         {"颜色", "黑色", false},
54         {"夏季冰丝汽车坐垫", "1088.00", true},
55     }
56 )
57 
58 
59 
60 func main() {
61     roadBike := 汽车{车身大小: "5037×2070×mm", 车的特性: 特斯拉}
62     mountainBike := 汽车{车身大小: "1678*1870*1398", 车的特性: 宝马}
63     recumbentBike := 汽车{车身大小: "4780*2030*1136", 车的特性: 兰博基尼}
64     fmt.Println(roadBike.备件方法())
65     fmt.Println(mountainBike.备件方法())
66     fmt.Println(recumbentBike.备件方法())
67     comboParts := 车的特性{}
68     comboParts = append(comboParts, mountainBike.车的特性...)
69     comboParts = append(comboParts, roadBike.车的特性...)
70     comboParts = append(comboParts, recumbentBike.车的特性...)
71 
72     fmt.Println(len(comboParts), comboParts[9:])
73     fmt.Println(comboParts.备件方法())
74 }
75 
76 
77 #以上代码执行结果如下:
78 [{Tesla_90D(加速时间) 100km/2.9s true}]
79 [{BMW M4敞篷轿跑车(加速时间) 100km/4.4s true} {价格 1,098,000美元 true} {倍耐力轮胎 兰博基尼Huracan LP580-2前轮原配 true} {夏季冰丝汽车坐垫 1088.00 true}]
80 [{Avetador(加速时间) 100km/2.8s true} {价格 648.80-801.15万 true} {夏季冰丝汽车坐垫 1088.00 true}]
81 11 [{颜色 黑色 false} {夏季冰丝汽车坐垫 1088.00 true}]
82 [{BMW M4敞篷轿跑车(加速时间) 100km/4.4s true} {价格 1,098,000美元 true} {倍耐力轮胎 兰博基尼Huracan LP580-2前轮原配 true} {夏季冰丝汽车坐垫 1088.00 true} {Tesla_90D(加速时间) 100km/2.9s true} {Avetador(加速时间) 100km/2.8s true} {价格 648.80-801.15万 true} {夏季冰丝汽车坐垫 1088.00 true}]

 

3.小试牛刀;

  还记得我们上次用面向过程编程写的一个学员管理系统吗?其实我们也可以稍微用结构体来装饰一下,实现一下功能。代码如下:

 

  1 /*
  2 #!/usr/bin/env gorun
  3 @author :yinzhengjie
  4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
  5 EMAIL:y1053419035@qq.com
  6 */
  7 
  8 package main
  9 
 10 import (
 11     "fmt"
 12     "encoding/json"
 13     "bufio"
 14     "os"
 15     "strings"
 16     "strconv"
 17     "io/ioutil"
 18 )
 19 
 20 type Student struct {  //定义一个名称为“Student”的结构统;
 21     ID int  //定义学员编号
 22     NAME string  //定义姓名
 23 }
 24 
 25 type ClassRoom struct { //定义一个名为“ClassRoom”的结构体;
 26     teacher string  //定义一个名称为“teacher”字符串类型的变量,
 27     students map[string]*Student  //定义一个变量名为“students”其类型为map的变量。
 28 }
 29 
 30 var    classrooms map[string]*ClassRoom //声明一个名为“classrooms”的变量,指定其类型是map,要注意的是map中的value是指针类型的结构统哟。
 31 
 32 var student_num = make(map[int]Student) //定义存取学生成员的函数,注意这里是要初始化的,字典在使用前必须初始化,不然会报错!
 33 
 34 func (s *ClassRoom)Delete()  {
 35 
 36 }
 37 
 38 
 39 func (c *ClassRoom) Add(args []string) error { //自定义添加学生信息的函数
 40     if len(args) != 2 {
 41         fmt.Println("您输入的字符串有问题,案例:add 01 bingan")
 42         return nil
 43     }
 44     id := args[0]
 45     student_name := args[1]
 46     student_id, _ := strconv.Atoi(id)
 47 
 48     for _, s := range student_num {
 49         if s.ID == student_id {
 50             fmt.Println("您输入的ID已经存在,请重新输入")
 51             return nil
 52         }
 53     }
 54     student_num[len(student_num)+1] = Student{ student_id,student_name}
 55     fmt.Println("Add successfully!!!")
 56     return nil
 57 }
 58 
 59 func (c *ClassRoom)Drop(args []string)error {
 60     if len(args) != 1 {
 61         fmt.Println("你愁啥?改学生ID压根就不存在!")
 62         return nil
 63     }
 64     id := args[0]
 65     student_id, _ := strconv.Atoi(id)
 66     for i, j := range student_num {
 67         if j.ID == student_id {
 68             delete(student_num, i) //删除map中该id所对应的key值。但是该功能需要完善!
 69             fmt.Println("delete successfully!")
 70             return nil
 71         }
 72     }
 73     fmt.Println("你愁啥?学生ID压根就不存在!")
 74     return nil
 75 }
 76 
 77 
 78 func (c *ClassRoom)Update(args []string)error  { //定义修改的函数
 79     if len(args) != 2 {
 80         fmt.Println("您输入的字符串有问题,案例:add 01 bingan")
 81         return nil
 82     }
 83     id := args[0]  //取出ID
 84     student_name := args[1]  //取出姓名
 85     student_id, _ := strconv.Atoi(id)  //将字符串ID变成数字类型。
 86     for i, j := range student_num {
 87         if j.ID == student_id {
 88             student_num[i] = Student{ student_id,student_name} //这其实就是一个赋值的过程。
 89             fmt.Println("update successfully!")
 90             return nil
 91         }
 92     }
 93     fmt.Println("你愁啥?学生ID压根就不存在!")
 94     return nil
 95 }
 96 
 97 
 98 func (c *ClassRoom)List(args []string)    error{ //给“ClassRoom”绑定一个“List”方法。
 99     if len(student_num) == 0 {
100         fmt.Println("数据库为空,请自行添加相关信息!")
101         return nil
102     }
103     for _,value := range student_num{
104         fmt.Printf("学员的姓名是:\\033[31;1m%s\\033[0m,学员编号是:\\033[31;1m%d\\033[0m\\n",value.NAME,value.ID)
105     }
106     return nil
107 }
108 
109 func (c *ClassRoom)Save(args []string)error  { //定义存取的函数
110     if len(args) == 0 {
111         fmt.Println("请输入您想要保存的文件名,例如:save student.txt")
112         return nil
113     }
114     file_name := args[0]
115     f, err := json.Marshal(student_num)  //把变量持久化,也就是将内存的变量存到硬盘的时进行的序列化的过程
116 
117     if err != nil {
118         fmt.Println("序列化出错啦!")
119     }
120     ioutil.WriteFile(file_name, f, 0644) //将数据写入硬盘,并制定文件的权限。
121     fmt.Println("写入成功")
122     return nil
123 }
124 
125 
126 func (c *ClassRoom)Load(args []string) error { //定义加载的函数。
127     if len(args) != 1 {
128         fmt.Println("输入错误,请重新输入.")
129         return nil
130     }
131     file_name := args[0]
132     s, _ := ioutil.ReadFile(file_name)
133     json.Unmarshal(s, &student_num)
134     fmt.Println("读取成功!")
135     return nil
136 }
137 
138 
139 func (c *ClassRoom)Exit(args []string) error { //定义对出的脚本
140     os.Exit(0) //里面的数字表示用户结束程序的返回值,返回0意味着程序是正常结束的。
141     return nil
142 }
143 
144 
145 
146 func main() {
147     classrooms = make(map[string]*ClassRoom) /*初始化字典,因为上面只定义没有初始化。初始化赋值这里不能加":="148     因为作用域不同(会将全局作用域的值给覆盖掉),加了得到的结果返回:"null".*/
149     fmt.Println("学生管理系统迷你版!")
150     f := bufio.NewReader(os.Stdin) //用它读取用户输入的内容
151     for {
152         fmt.Print("请选择您要去的教室")
153         fmt.Print("请输入:>>>")
154         line, _ := f.ReadString(\'\\n\')   //将读取的内容按照"\\n"换行符来切分,注意里面是单引号哟!
155         line = strings.Trim(line, "\\n") //表示只脱去换行符:"\\n",你可以自定义脱去字符,等效于line = strings.TrimSpace(line)
156         content := strings.Fields(line) //按照空格将得来的字符串做成一个切片。
157 
158         if len(content) == 0 { //脱去空格
159             continue
以上是关于GO语言的进阶之路-面向对象编程的主要内容,如果未能解决你的问题,请参考以下文章

Python之路:面向对象(进阶)

Java 从入门到进阶之路(十八)

Python之路,Day7 - 面向对象编程进阶

go语音之进阶篇方法面向过程和对象函数的区别

Python学习之路——Day8(面向对象进阶)

Python高级工程师之路入门+进阶+实战+爬虫+数据分析整套教程