Go中的面向“对象” | Go 主题月

540 阅读5分钟

前言

对于面向对象这个东西,我想大家都是非常熟悉的了,我们常说的面向对象,就是创建一个类,然后实例化这个类来进行一系列的操作,例如,在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函数,作为入口函数。

封装一个小功能试试看

这里我们把上面的对于切片处理的函数,给它封装起来,方便我们在其它地方可以用来调用它,那么具体做法就是下面这样

WX20210325-144854.png

在根目录下,我创建了一个叫做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里面是怎么实现的呢?

下面我会介绍一种较多人使用的扩展这个结构体类型的方法

组合方式(最多人用)

组合的方式是最多人用的一种方式,也是相对来说易读性更好的一种方式,具体实现如下

WX20210325-152029.png

这里我新建了一个目录,用于对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语言的初学者,有些地方可能讲的不是特别好,如果有什么讲的不好的欢迎评论指正哈!

下载.jpeg