Golang 进阶1 —— 面向对象

时间:2024-10-07 08:31:58

Golang 进阶1 —— 面向对象

注意,该文档只适合有编程基础的同学,这里的go教程只给出有区别的知识点

go语言面向对象说明

  1. Golang与传统的面向编程的语言有区别,并不是纯粹的面向对象编程(OOP)。所以更加准确的说法是Golang支持面向对象编程特性。
  2. Golang本身是没有类(class)的概念的, Golang的结构体和其他编程语言的类(class)有同等的地位, 可以理解为Golang是基于struct来实现OOP特性的。
  3. Golang面向编程非常整洁,去掉了传统OOP语言的方法重载、构造函数和析构函数、隐藏this指针etc。
  4. Golang 仍然有面向对象编程的继承、封装和多态的特性,只是实现的方式和其他OOP语言不一样,比如继承Golang没有extends关键字,继承是通过匿名字段来实现的。

1. 结构体定义

1.1 main文件
type Student struct {
	Name  string
	Age   int
	School string
}
func main() {
	// 实例创建:方式1
	var stu1 Student
	stu1.Name = "张三"
	stu1.Age = 18
	stu1.School = "清华大学"
	fmt.Println(stu1)

	// 实例创建:方式2
	stu2 := Student{
		Name: "李四", 
		Age: 19, 
		School: "北京大学"}
	fmt.Println(stu2)
	
	// 实例创建:方式3
	var stu3 *Student = new(Student)
	// stu3 是指针(指向的是地址), 应该给这个地址所指向的对象进行赋值操作
	(*stu3).Name = "王五"
	(*stu3).Age = 20
	(*stu3).School = "北京大学"
	fmt.Println(*stu3)

	// 实例创建:方式4
	var stu4 = &Student{
		Name: "赵六", 
		Age: 21, 
		School: "北京大学"}
	fmt.Println(*stu4)
}
1.2 输出结果
(base) PS E:\Goproject\src\gocode\testproject01> go run ./main/main.go
{张三 18 清华大学}
{李四 19 北京大学}
{王五 20 北京大学}
{赵六 21 北京大学}
1.3 结构体方法
type Student struct {
	Name  string
	Age   int
	School string
}
// 1、方法是作用在指定的数据类型上、和指定的类型进行绑定,因此自定义的类型都可以有方法	,而不仅仅是struct
// 2、方法的声明格式:func (变量名 数据类型) 方法名(参数列表) 返回值列表{方法体}
func (stu Student) Print() { // 给Student结构体绑定方法Print()
	stu.Name = "李四"
	fmt.Println("姓名:", stu.Name)
	fmt.Println("年龄:", stu.Age)
	fmt.Println("学校:", stu.School)
}

// 3、结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
// 4、 如果程序员希望在方法改变变量的值,可以通过结构体指针方式来处理
func (stu *Student) Print2() { // 给Student结构体绑定方法Print2()
	stu.Name = "笑笑"
	fmt.Println("姓名:", (*stu).Name)
	fmt.Println("年龄:", (*stu).Age)
	fmt.Println("学校:", (*stu).School)
}


// 5、如果一个类型实现了String() 这个方法的话, 那么fmt.Print() 默认会调用这个变量的String() 进行输出
// 以后定义结构体的话,常用于定义String()作为输出结构体信息的方法,会自动调用
func (stu *Student) String() string {
	return fmt.Sprintf("姓名:%s,年龄:%d,学校:%s", (*stu).Name, (*stu).Age, (*stu).School)
}

func main() {
	var stu1 Student
	stu1.Name = "张三"
	stu1.Age = 18
	stu1.School = "清华大学"
 
	// 值传递
	stu1.Print()
	fmt.Println("main中 stu1.Name=", stu1.Name) 
	fmt.Println("--------------------------------")

	// 引用传递
	(&stu1).Print2()
	fmt.Println("main中 stu1.Name=", stu1.Name) 

	// 测试String() 方法
	fmt.Println("--------------------------------")
	fmt.Println(&stu1)
}
1.4 输出结果
(base) PS E:\Goproject\src\gocode\testproject01> go run ./main/main.go
姓名: 李四
年龄: 18
学校: 清华大学
main中 stu1.Name= 张三
--------------------------------
姓名: 笑笑
年龄: 18
学校: 清华大学
main中 stu1.Name= 笑笑
--------------------------------
姓名:笑笑,年龄:18,学校:清华大学
1.5 方法与函数的区别
type Student struct {
	Name  string
	Age   int
	School string
}

/* (1) 绑定指定类型:
方法: 需要绑定指定数据类型
函数: 不需要绑定指定类型
(2) 调用的方式不一样:
函数的调用方式: 函数名(实参列表)
方法的调用方式: 变量.方法名(实参列表) 
(3) 调用函数时, 实参是什么类型就得传入什么类型
(4) 调用方式时, 混用不会报错 (&stu).method01(), stu.method01() 效果是相同的
*/

// 方法
func (stu Student) Print() { // 给Student结构体绑定方法Print()
	fmt.Println("姓名:", stu.Name)
	fmt.Println("年龄:", stu.Age)
	fmt.Println("学校:", stu.School)
}
// 函数
func Print2(stu Student) {
	fmt.Println("姓名:", stu.Name)
	fmt.Println("年龄:", stu.Age)
	fmt.Println("学校:", stu.School)
}

func main() {
	var stu1 Student
	stu1.Name = "张三"
	stu1.Age = 18
	stu1.School = "清华大学"
	// 方法调用
	stu1.Print()
	fmt.Println("--------------------------------")
	// 函数调用
	Print2(stu1)
}
1.6 输出结果
(base) PS E:\Goproject\src\gocode\testproject01> go run ./main/main.go
姓名: 张三
年龄: 18
学校: 清华大学
--------------------------------
姓名: 张三
年龄: 18
学校: 清华大学
1.7 赋值操作
func main() {
	// 赋值操作, 方式1 注意:赋值顺序必须和结构体定义的顺序一致
	var stu1 Student = Student{Name:"张三", Age:18, School:"清华大学"}
	fmt.Println(stu1)

	// 赋值操作, 方式2 按照指定的类型进行赋值, 此时不用按顺序赋值
	var stu2 Student = Student{
		School:"北京大学",
		Name:"李四",
		Age:19,
	}
	fmt.Println(stu2)

	// 赋值操作, 方式3 想要返回结构体的指针类型
	var stu3 *Student = &Student{
		School:"北京大学",
		Name:"王五",
		Age:20,
	}
	fmt.Println(*stu3)
}
1.8 输出结果
(base) PS E:\Goproject\src\gocode\testproject01> go run ./main/main.go
{张三 18 清华大学}
{李四 19 北京大学}
{王五 20 北京大学}
1.9 跨包引用结构体实例
1.9.1 Student.go 文件
package Model

import (
	"fmt"
)

type Student struct {
	Name  string
	Age   int
	School string
}

func (stu *Student) String() string {
	return fmt.Sprintf("姓名:%s,年龄:%d,学校:%s", (*stu).Name, (*stu).Age, (*stu).School)
}
1.9.2 main.go 文件
import (
	"fmt"
	"gocode/testproject01/Student"
)


func main() {
    stu := Model.Student{
		School:"北京大学",
		Name:"李四",
		Age:19,
	}
	fmt.Println(&stu)
}
1.9.3 输出结果
(base) PS E:\Goproject\src\gocode\testproject01> go run ./main/main.go
姓名:李四,年龄:19,学校:北京大学
1.10 工厂模式: 解决结构体首字母小写,但是跨包访问没问题
1.10.1 Student.go 文件
package Model

import (
	"fmt"
)

type Student struct {
	Name  string
	Age   int
	School string
}

func (stu *Student) String() string {
	return fmt.Sprintf("姓名:%s,年龄:%d,学校:%s", (*stu).Name, (*stu).Age, (*stu).School)
}
1.10.2 main.go 文件
import (
	"fmt"
	"gocode/testproject01/Student"
)


func main() {
    stu := Model.Student{
		School:"北京大学",
		Name:"李四",
		Age:19,
	}
	fmt.Println(&stu)
}
1.10.3 输出结果
(base) PS E:\Goproject\src\gocode\testproject01> go run ./main/main.go
姓名:笑笑,年龄:18,学校:ncu
1.11 封装

封装(encapsulation)就是把抽象出来的字段和对字段的操作封装在一起, 数据被保护在内部,程序的其他包只有通过被授权的操作方法,才能对字段进行操作

封装的好处: (1) 隐藏实现细节(2)可以对数据进行验证, 保证安全合理

Golang 实现封装:

  • 将结构体、 字段(属性) 的首字母小写(其他包) 不能使用, 类似于private(实际开发不小写也可能)

  • 给结构体所在包提供一个工厂模式的函数, 首字母大写(类似于一个构造函数)

  • 提供一个首字母大写的set方法(类似于其他语言的public),用于对属性判断赋值

    func (var 结构体类型名) SetXXX (参数列表) {

    ​ //加入数据验证的业务逻辑

    ​ var.Age = 参数

    }

  • 提供一个首字母大写的Get方法(类似于其他语言的public), 用于获取属性的值

    func (var 结构体类型名) GetXXX (参数列表) {

    ​ //加入数据验证的业务逻辑

    ​ return var.字段

    }

1.11.1 Student.go 文件
// 结构体名字小写开头, 避免被其他包访问
type student struct {
	name  string //其他包不能访问
	age   int
}

func (stu *student) String() string {
	return fmt.Sprintf("姓名:%s,年龄:%d", (*stu).name, (*stu).age)
}

//工厂模式, 类似于构造器
func NewStudent () *student {
	// 内部调用并返回结构体
	return &student{}
}

//定义set和get函数对name、age进行封装, 确保被封装字段的安全合理性

func (stu *student) SetName(name string) {
	(*stu).name = name
}

func (stu *student) GetName() string {
	return (*stu).name
}

func (stu *student) SetAge(age int) {
	if age < 0 || age > 200 {
		fmt.Println("年龄不合法,请重新输入!")
	} else {
		(*stu).age = age
	}
}
func (stu *student) GetAge() int {
	return (*stu).age
}
1.11.2 main.go 文件
import (
	"fmt"
	"gocode/testproject01/Student"
)


func main() {
	//返回的是指针
	stu := Model.NewStudent()
	stu.SetName("张三")
	stu.SetAge(18)
	// 想要调用Spring() 函数就不用在 &stu了
	fmt.Println(stu)
}
1.11.3 输出结果
(base) PS E:\Goproject\src\gocode\testproject01> go run ./main/main.go
姓名:张三,年龄:18
1.12 继承(提高代码复用性)
1.12.1 Animal.go 文件
type Animal struct {
	Age int
	Weight float32 
}

// 给Animal结构体添加方法: 喊叫
func (animal *Animal) Shout() {
	fmt.Println("Animal shout")
}

// 给Animal结构体添加方法: 自我展示
func (animal *Animal) Show() {
	fmt.Printf("Animal Age:%d, Weight:%f\n", animal.Age, animal.Weight)
}

//定义结构体: Cat

type Cat struct {
	Animal //为了复用性, 体现继承思维, 加入匿名函数: Animal
	Color string
}

// 对 Cat 结构体添加方法: 抓老鼠
func (cat *Cat) CatchMouse() {
	fmt.Println("Cat Catch Mouse")
}

func (cat *Cat) Shout() {
	fmt.Println("Cat shout")
}
1.12.2 main.go 文件
import (
	"fmt"
	"gocode/testproject01/Animal"
)


func main() {
	//返回的是指针
	cat := &Model.Cat {}
	cat.Age = 10
	cat.Weight = 10.0
	cat.Color = "white"
	fmt.Println(*cat)
	
	cat.CatchMouse()
	cat.Shout() //正常访问的话是就近原则,在Cat中找不到的话会从匿名类中找
	cat.Animal.Shout()
}
1.12.3 输出结果
(base) PS E:\Goproject\src\gocode\testproject01> go run ./main/main.go
{{10 10} white}
Cat Catch Mouse
Cat shout
Animal shout
// Golang 支持多继承(多个匿名类实现, 如Animal1, Animal2), 但是实际开发中不建议 
1.13 接口

(1)接口可以定义一组方法,但不需要实现,不需要方法体。并且接口中不能包含任何变量。到某个定义类型要使用的时候,再根据具体情况把这些方法具体实现出来

(2)实现接口要实现所有方法才是实现

(3)Golang中的接口是基于方法的,不是基于接口的

1.13.1 main.go 文件
// 接口的定义: 定义规则、 定义规范、 定义某种能力:
type sayHello interface {
	// 接口中定义的方法,接口的实现者,必须实现这些方法
	sayHello()
}
 
// 接口的实现:定义一个结构体
// 中国人
type Chinese struct {

}

// 实现接口的方法
func (chinese *Chinese) sayHello() {
	fmt.Println("中国人:你好")
} 


// 美国人
type American struct {

}

func (chinese *American) sayHello() {
	fmt.Println("美国人:hi")
} 

// 定义一个函数: 专门用来各国人打招呼的函数, 接收具备SayHello接口能力的变量:
func greet(sayHello sayHello) {
	sayHello.sayHello()
}


func main() {
	chinese := &Chinese{}
	greet(chinese)

	american := &American{}
	greet(american)
}

1.13.2输出结果
(base) PS E:\Goproject\src\gocode\testproject01> go run ./main/main.go
中国人:你好
美国人:hi
1.14 断言
1.14.1 main文件
// 接口的定义: 定义规则、 定义规范、 定义某种能力:
type sayHello interface {
	// 接口中定义的方法,接口的实现者,必须实现这些方法
	sayHello()
}
 
// 接口的实现:定义一个结构体
// 中国人
type Chinese struct {

}

// 实现接口的方法
func (chinese *Chinese) sayHello() {
	fmt.Println("中国人:你好")
} 

func (chinese *Chinese) sayBye() {
	fmt.Println("中国人:再见")
}


// 美国人
type American struct {

}

func (chinese *American) sayHello() {
	fmt.Println("美国人:hi")
} 

// 定义一个函数: 专门用来各国人打招呼的函数, 接收具备SayHello接口能力的变量:
func greet(sayHello sayHello) {
	sayHello.sayHello()
	// sayHello.sayBye() sayBye是中国人特有方法,美国人没有,所以会报错
	/*(base) PS E:\Goproject\src\gocode\testproject01> go run ./main/main.go
		# command-line-arguments
		main\main.go:41:11: sayHello.sayBye undefined (type sayHello has no field or method sayBye)
		此时可以采用断言解决, 返回2个变量,当前变量为nil,ok为false
	*/
	if chinese, ok := sayHello.(*Chinese); ok {
		chinese.sayBye()
	} 
}


func main() {
	// chinese := &Chinese{}
	// greet(chinese)

	american := &American{}
	greet(american)
}

1.14.2 输出结果
(base) PS E:\Goproject\src\gocode\testproject01> go run ./main/main.go
中国人:你好
中国人:再见
美国人:hi