Go语言入门18:面向对象

Keywords: #技术 #Golang #Go 入门笔记
Table of Contents

Go 语言入门基础学习笔记之 Go 语言的面向对象

golang

面向对象特征

Go 语言中的对象和类的概念,实际上就是一个结构体绑定对应的方法来实现的。

封装

Go 语言的封装概念,事实上就是针对的封装。

创建一个普通的结构体来定义类:

type Hero struct {
    Name string
    Ad int
    Level int
}

如果类名首字母大写,表示其他包能够访问,是一个公共类。同理类中的成员变量也需要大写来保证外部其他包能够访问,否则只能当前程序能够访问使用。

Go 语言通过首字母大小写来区别是否对外部开放。

如上一节函数与成员方法,给结构体绑定一个成员方法:

func (h *Hero) Show() {
    fmt.Println("Name:", h.Name)
    fmt.Println("Ad:", h.Ad)
    fmt.Println("level:", h.level)
}

func (h *Hero) GetName() string {
    return h.Name
}

func (h *Hero) SetName(name string) {
    h.Name = name
}

如果传参写成 h Hero 只是调用该对象方法的一个副本,即值传递(拷贝),改变的不是原来变量的值。同时,传参时最好不要写成 this 或者 self 的形式。

func main() {
    h := Hero{Name: "Lina", Ad: 100, level: 1}
    h.Show()
    fmt.Println(h.GetName())
    h.SetName("Luna")
    fmt.Println(h.GetName())
}

// 输出结果
Name: Lina
Ad: 100 
level: 1
Lina    
Luna

继承

实际上,Go 语言不存在狭义上的继承,但是在 Go 语言中可以把一个结构体作为另一个结构体的成员变量的类型。即可以把一个结构体类型当成一个基础的数据类型。比如:

定义一个父类和方法:

// 定义父类
type Human struct {
    name string
    sex  string
}

// 定义父类的方法
func (h *Human) Eat() {
    fmt.Println("Human Eat()...")
}

func (h *Human) Walk() {
    fmt.Println("Human Walk()...")
}

这时父类的 Human 这种结构体类型就可以作为一种基础的数据类型在子类中应用。

// 定义子类
type SuperMan struct {
    Human // 继承Human类中的字段和方法
    level int
}

在子类中,也可以重写父类的方法或是添加一个新的方法。

// 重写父类的方法
func (s *SuperMan) Eat() {
    fmt.Println("SuperMan Eat()...")
}

// 定义子类的新方法
func (s *SuperMan) Fly() {
    fmt.Println("SuperMan Fly()...")
}

// 定义子类的新方法
func (s *SuperMan) Print() {
    fmt.Println("name:", s.name)
    fmt.Println("sex:", s.sex)
    fmt.Println("level:", s.level)
}
func main() {
    var s SuperMan
    s.name = "li4"
    s.sex = female
    s.level = 88
    //也可以 s := SuperMan{Human{"li4", "female"}, 88}
    s.Eat()  // SuperMan Eat()...
    s.Walk() // Human Walk()...
    s.Fly()  // SuperMan Fly()...
    s.Print()
}

// 输出结果
SuperMan Eat()...
Human Walk()...
SuperMan Fly()...
name: li4
sex: female
level: 88

多态

Go 语言实现多态需要利用到接口,实现多态有几个基本的要求:

  1. 有一个父类(接口)
  2. 有子类,实现了父类(接口)中的全部接口方法
  3. 父类(接口)的变量(指针) 指向 (引用) 子类的具体数据变量(接口是一个指针)

首先,需要定义一个接口,接口中包含一些基本的方法:

type AnimalIF interface {
    Sleep()
    GetColor() string // 获取动物的颜色
    GetType() string // 获取动物的种类
}

接着,我们可以定义一个具体的类:

type Cat struct {
    color string // 猫的颜色
}

随后,我们需要让这个类实现这个接口,即实现接口全部的方法:

func (c *Cat) Sleep() {
    fmt.Println("Cat is Sleep")
}

func (c *Cat) GetColor() string {
    return c.color
}

func (c *Cat) GetType() string {
    return "Cat"
}

我们还可以定义另外一个具体的类:

type Dog struct {
    color string // 狗的颜色
}

同样让这个类实现上面的接口:

func (d *Dog) Sleep() {
    fmt.Println("Dog is Sleep")
}

func (d *Dog) GetColor() string {
    return d.color
}

func (d *Dog) GetType() string {
    return "Dog"
}

由此,两个类都实现了同一个接口,我们可以通过接口来实现多态

func showAnimal(animal AnimalIF) { // 传递接口类型
    animalType := animal.GetType()
    animalColor := animal.GetColor()
    fmt.Printf("This is a %s, its color is %s\n", animalType, animalColor)
    animal.Sleep()
}
func main() {
    cat := &Cat{color: "white"}
    dog := &Dog{color: "black"}
    
    showAnimal(cat)
    showAnimal(dog) 
}

// 输出结果
This is a Cat, its color is white
Cat is Sleep
This is a Dog, its color is black
Dog is Sleep

在上面这段代码中,catdog 实际上已经是一个接口类型的变量,这是因为 Cat{color: "white"}Dog{color: "black"} 都是一个实现了接口中所有方法的结构体,事实上它已经是一个接口了,但是由于结构体是一个值类型,而接口时一个引用类型,因此可以把结构体的地址赋值给一个接口变量(接口是一个指针),于是 catdog 就是一个接口变量。

如果一个结构体并没有实现接口中的所有方法,那么它就不能是一个接口类型,因此如果进行上面的操作时,就会发生报错,提示“它不是一个接口类型,因为没有实现 Sleep 方法”


参考课程:

  1. 8小时转职Golang工程师
  2. Go语言教程 | 菜鸟教程