Golang 进阶1 —— 面向对象
注意,该文档只适合有编程基础的同学,这里的go教程只给出有区别的知识点
go语言面向对象说明
- Golang与传统的面向编程的语言有区别,并不是纯粹的面向对象编程(OOP)。所以更加准确的说法是Golang支持面向对象编程特性。
- Golang本身是没有类(class)的概念的, Golang的结构体和其他编程语言的类(class)有同等的地位, 可以理解为Golang是基于struct来实现OOP特性的。
- Golang面向编程非常整洁,去掉了传统OOP语言的方法重载、构造函数和析构函数、隐藏this指针etc。
- 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