Go的面向特征学习笔记 | 青训营笔记

68 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第7篇笔记。

一、封装

1.1、定义对象以及创建对象

package main
​
import "fmt"//定义类型
type T struct {
    name string
    sex int
}
​
func main() {
    //定义对象
    //创建对象方式一:键值对形式
    t1 := T{name: "changlu",sex: 123}
    //创建对象方式二:直接顺序传入
    t2 := T{"changlu",123}
    fmt.Println(t1, t2)
    //创建对象方式三:不带有属性值
    s := new(Student)
}

image-20220430231057026

类方法调用(T与*T区别)

提问:对象的方法一个采用T,一个采用*T,有什么区别?

结论:前者是值传递,后者是引用传递,调用后者*T才会对调用对象的属性进行改变!

package main
​
import "fmt"//定义对象
type T struct {
    name string
}
​
//这里是值传递,不会对实际的对象修改值
func (t T) method1()  {
    t.name = "liner"
}
​
//这是引用传递,会对原始对象进行修改
func (t *T) method2()  {
    t.name = "liner liner"
}
​
func main() {
    t := T{"changlu"}
    //测试方法一
    fmt.Println("method1前:", t.name)
    t.method1()
    fmt.Println("method1后:", t.name)
​
    //测试方法二
    fmt.Println("method1前:", t.name)
    t.method2()
    fmt.Println("method2后:", t.name)
}

image-20220430231600551


1.2、方法值:绑定了对象值的方法

示例

package main
​
import "fmt"//定义对象
type Student struct {
    age int
}
​
//这里是值传递,不会对实际的对象修改值
func (s Student) ageDifference(student Student) int {
    return s.age - student.age
}
​
func main() {
    student1 := Student{18}
    student2 := Student{19}
​
    //方式一:对象.方法(其他对象)
    fmt.Println(student1.ageDifference(student2))
​
    //方式二:拿到带有对象的方法
    stu2Diff := student2.ageDifference
    fmt.Println(stu2Diff(student1))  //这里实际上就相当于 student2.ageDifference(student1)
​
}

image-20220430232550341

应用场景

在一个包的API需要一个函数值、且调用方希望操作的是某一个绑定了对象的方法的话,方法"值"会非常实用.

举例来说,下面例子中的time.AfterFunc这个函数的功能是在指定的延迟时间之后来执行一个(译注:另外的)函数。且这个函数操作的是一个Rocket对象r:

type Rocket struct { /* ... */ }
func (r *Rocket) Launch() { /* ... */ }
r := new(Rocket)

比较麻烦写法

time.AfterFunc(10 * time.Second, func() { r.Launch() })

简洁写法:直接传入函数(推荐)

time.AfterFunc(10 * time.Second, r.Launch)

1.3、方法表达式

好处:灵活来指定调用者与目标调用者。

测试

定义方法表达式与使用:

package main
​
import "fmt"//定义对象
type Student struct {
    age int
}
​
//这里是值传递,不会对实际的对象修改值
func (s Student) ageDifference(student Student) int {
    return s.age - student.age
}
​
func main() {
    //获取方式:通过struct名拿到的,所以其为:方法表达式
    ageDiff := Student.ageDifference
    //使用方法表达式来进行前后对象的调用
    s1 := Student{18}
    s2 := Student{19}
    fmt.Println(ageDiff(s1, s2))  //等价于:s1.ageDifference(s2)。妙!
}

image-20220430233354773

使用场景

需求:使用一个通用的方法来通过参数传递的形式进行年龄加或者减。

  • 使用一个通用方法来根据传入的参数true或false来接收指定的方法表达式并进行执行。
package main
​
import "fmt"//定义对象
type Student struct {
    age int
}
​
//年龄加法
func (s Student)add(student Student) int {
    return s.age + student.age
}
​
//年龄减法
func (s Student)minus(student Student) int {
    return s.age - student.age
}
​
//计算(使用方法表达式来进行接收)
func calculate(s1 Student, s2 Student, oper bool) int {
    //定义一个方法表达式
    var op func(s1,s2 Student) int //s1表示所属对象;s2表示方法参数;int表示返回值
    //若是true则使用add,若是false则使用minus
    if oper {
        op = Student.add
    }else {
        op = Student.minus
    }
    return op(s1, s2)
}
​
func main() {
    //使用方法表达式来进行前后对象的调用
    s1 := Student{18}
    s2 := Student{19}
    fmt.Println(calculate(s1,s2, true))  //进行年龄相加
    fmt.Println(calculate(s1,s2, false))  //进行年龄相减
}

image-20220430234402911


二、继承

子类继承父类,并重写方法:

package main

import "fmt"

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

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

func (this *Human) walk()  {
	fmt.Println("Human walk ...")
}

//定义子类
type Man struct {
	Human
	age int
}

//重写父类方法(实现多态)
func (this *Man) Eat()  {
	fmt.Println("Man eat ...")
}

func (this *Man) walk()  {
	fmt.Println("Man walk ...")
}

func (this *Man) getAge()  {
	fmt.Println("Man age is ", this.age)
}


func main() {
	//父类
	var human Human
	human = Human{"人类", "男"}
	human.Eat()
	human.walk()

	//子类
	var man Man
	man.age = 18
	//使用父类的方法
	man.Eat()
	man.walk()
	man.getAge()

}

image-20220501221454557

三、多态

3.1、多态案例

案例:定义一个接口,接着两个类去实现接口方法,通过变量来修改引用指向,实现多态。

  • 规范1:一旦实现某个接口中的方法,那么其他接口方法都要实现
  • 规范2:给变量传递一定要传递指针,如:animal = &Dog{"red"}
package main

import "fmt"

//定义一个接口(其中定义方法)
type AnimalIF interface {
	Sleep()
	getColor()
	GetType()
}

//狗实现接口:func (this *Dog) Sleep(){}
type Dog struct {
	color string
}

//规范1:一旦实现某个接口中的方法,那么其他接口方法都要实现
func (this *Dog) getColor() {
	fmt.Println("Dog color:", this.color)
}

func (this *Dog) GetType() {
	fmt.Println("Dog")
}


func (this *Dog) Sleep()  {
	fmt.Println("Dog 睡觉")
}

//猫也定义一套实现
type Cat struct {
	color string
}
func (this *Cat) getColor() {
	fmt.Println("Cat color:", this.color)
}

func (this *Cat) GetType() {
	fmt.Println("Cat")
}


func (this *Cat) Sleep()  {
	fmt.Println("Cat 睡觉")
}


func main() {
	var animal AnimalIF
	//规范2:给变量传递一定要传递指针
	animal = &Dog{"red"}
	animal.Sleep()
	animal.getColor()
	animal.GetType()

	//将接口引用指向猫
	animal = &Cat{"yellor"}
	animal.Sleep()
	animal.getColor()
	animal.GetType()
}

image-20220501222216957


3.2、多态在方法中使用

案例:我们可以在方法中使用多态来传递对应的实现引用。

//多态指针传入
func testAnimalMethods(animal AnimalIF)  {
	animal.Sleep()
	animal.getColor()
	animal.GetType()
}


func main() {
	var animal AnimalIF
	animal = &Dog{"red"}
	testAnimalMethods(animal)
	animal = &Cat{"yellor"}
	testAnimalMethods(animal)
}

image-20220501222528667

参考

[1] 7、面向对象特征