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:[email protected] 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:[email protected] 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:[email protected] 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:[email protected] 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:[email protected] 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:[email protected] 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 clas-s-rooms map[string]*ClassRoom //声明一个名为“clas-s-rooms”的变量,指定其类型是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 clas-s-rooms = 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
全部评论
请发表评论