前言
对于面向对象这个东西,我想大家都是非常熟悉的了,我们常说的面向对象,就是创建一个类,然后实例化这个类来进行一系列的操作,例如,在Javascript中,则是通过class来实现创建一个类,而这一个类可以有诸如继承,多态等等
在Go中,同样可以对一个类型进行封装,但是在Go中,面向对象它只是支持封装,并不支持多态和继承等,在Go中,要想控制Go中的构造函数,可以通过例如工厂函数这样的方式来实现。
Go中的面向“对象”
在Go中,想要生成一个结构体,可以通过type来实现,例如下面这样
type class struct {
value string
}
func main() {
r := class{"hello world"}
fmt.Print(r) // {hello world}
}
注意事项
在Go中的命名是对首字母的大小写敏感的,它不能像是Javascript那样采用各种各样的命名方式。
在Go中,当首字母为大写时,以为这个方法可以在其他去模块(这个后面会讲到)中被调用,也就是公有的,当首字母为小写的时候,它是私有的,也就是只有当前这个文件可以调用它。
工厂函数在Go中的运用
在Go里面,虽然说结构体很多时候已经可以满足我们的大部分需求,但是有些时候,我们要想自定义的去控制这个构造函数的生成的时候,可以通过一个工厂函数的方式来实现。
type class struct {
value string
left, right *class //建议一个左右指针
}
func createClass(value string) *class {
//这是一个工厂函数
return &class{value:value}
}
func main() {
var r class
r = class{value: "hello"}
r.left = createClass("world")
fmt.Print(r.left) // &{world <nil> <nil>}
}
给结构体添加方法
在其他语言中,想要给结构体增加方法有很多方式,例如在Javascript中的stact等,但是在Go中,想要给一个结构体增加一个方法的方式有点特别。
我们知道Go其实是一门函数式编程,几乎所有的东西都可以是一个函数,这里也一样,例如,我们想要实现一个对于分片(在其他语言中的数组)的push和pop的功能,这里我们可以用下面的方法来实现
type list []int
func (r *list) push(v int) {
*r = append(*r,v)
}
func (r *list) pop() int {
v := (*r)[0]
*r = (*r)[1:]
return v
}
func main() {
arr := list{1,2,3,4}
arr.push(5)
fmt.Print(arr) //[1 2 3 4 5]
t := arr.pop()
fmt.Println("\n",t) // 1
}
这里我们可以看到通过在函数前面加多一个括号的方式,就可以在Go中,给一个结构体添加方法。
其实本质上来说,这个是属于Go的一个语法糖,它意思是相当于下面的样子,理解上可能会更方便我们理解,但是既然我们都开始学习Go了,那么就多使用它所提供的语法糖不是更好吗。
type list []int
func push(v int, r *list) {
*r = append(*r,v)
}
func pop(r *list) int {
v := (*r)[0]
*r = (*r)[1:]
return v
}
func main() {
arr := list{1,2,3,4}
push(5,&arr)
fmt.Print(arr) //[1 2 3 4 5]
t := pop(&arr)
fmt.Println("\n",t) // 1
}
Go中的封装
在Go中,同样也提供对于一些功能的封装的功能,例如上面那个例子,我们想要把它封装起来,在其他地方也可以调用它,这里就需要运用到我前面所提到的大小写了,大写代表公用,小写代表私有(只有定义它的文件可以使用)
注意事项⚠️
在Go中,同样的每个封装起来的包都是一个目录文件,这个跟node的module一样。
同时每个包只能又一个main函数,作为入口函数。
封装一个小功能试试看
这里我们把上面的对于切片处理的函数,给它封装起来,方便我们在其它地方可以用来调用它,那么具体做法就是下面这样
在根目录下,我创建了一个叫做update的文件目录,里面放一个Go文件,用来封装这个方法,同时外部的一个文件用来调用这个方法,具体代码是这样的
// update.go
package update //这个名字必须是目录的名字
type Array []int
func (r *Array) Push(v int) {
*r = append(*r,v)
}
package main
import (
"fmt"
"testProject/update"
)
func main() {
r := update.Array{1,2,3,4}
r.Push(2)
fmt.Print(r) // [1 2 3 4 2]
}
这里我们可以看到,通过这样的方式,就可以对一个方法进行封装了。
Go中是如何实现“继承”的
相信大家对于继承这个概念并不会陌生了,几乎所有面向对象的编程中,都会有继承这个概念,例如javascrip中的原型链继承,换成语法糖来就是extend。
那么我刚才也说了,在Go里面,对于结构体是没有继承的,那么如果说面对一个前人写好的结构体,你要增加一点什么东西的以后,在其他语言里面,可以通过继承来实现,那么在Go里面是怎么实现的呢?
下面我会介绍一种较多人使用的扩展这个结构体类型的方法
组合方式(最多人用)
组合的方式是最多人用的一种方式,也是相对来说易读性更好的一种方式,具体实现如下
这里我新建了一个目录,用于对update进行扩充,而我的newUpdate.go里面代码写法是这样的
package newUpdate
import "testProject/update"
type NodeArray update.Array
func (r *NodeArray) Pop() int {
d := (*r)[0]
*r = (*r)[1:]
return d
}
这样就完成了在Go中对一个功能的简单扩充,使用起来也是一样的
import (
"fmt"
"testProject/newUpdate"
"testProject/update"
)
func main() {
r := update.Array{1,2,3,4}
a := newUpdate.NodeArray(r)
r.Push(2)
fmt.Print(r)
fmt.Println(a.Pop()) // 1
}
这种方法的好处就是对于阅读起来会比较友好,但是可能会增加一部分的冗余代码。
其实还有很多,例如内嵌,别名等等这些方式同样也可以使用,这里我就不再说明了,本质来说还是大同小异。
最后
作为一名Go语言的初学者,有些地方可能讲的不是特别好,如果有什么讲的不好的欢迎评论指正哈!