基本介绍

Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它 OOP 语言不一 样,下面我们一一为同学们进行详细的讲解 Golang 的三大特性是如何实现的。

封装介绍

封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其 它包只有通过被授权的操作(方法),才能对字段进行操作
在这里插入图片描述
封装的理解和好处

  1. 隐藏实现细节
  2. 提可以对数据进行验证,保证安全合理(Age)

如何体现封装

  1. 对结构体中的属性进行封装
  2. 通过方法,包实现封装

封装的实现步骤

 func (var	结构体类型名) SetXxx(参数列表) (返回值列表) {//加入数据验证的业务逻辑 var.字段 =  参数}
 func (var  结构体类型名) GetXxx() {return var.age;}

特别说明:在 Golang 开发中并没有特别强调封装,这点并不像 Java. 所以提醒学过 java 的朋友, 不用总是用 java 的语法特性来看待 Golang, Golang 本身对面向对象的特性做了简化的.

快速入门案例

看一个案例 请大家看一个程序(person.go),不能随便查看人的年龄,工资等隐私,并对输入的年龄进行合理的验证。设计: model 包(person.go) main 包(main.go 调用 Person 结构体)

代码实现
model/person.go
在这里插入图片描述在这里插入图片描述

main/main.go
在这里插入图片描述

面向对象编程三大特性-继承

看一个问题,引出继承的必要性

一个小问题,看个学生考试系统的程序 extends01.go,提出代码复用的问题
在这里插入图片描述
code

package mainimport ("fmt")type Pupil struct{Name stringAge intScore int
}func (p *Pupil) ShowInfo() {fmt.Printf("学生姓名=%v 年龄=%v  成绩=%v\n" , (*p).Name, (*p).Age, (*p).Score)
}func (p *Pupil) SetScore(score int) {p.Score = score
}func (*Pupil) testing(){fmt.Printf("小学生正在考试\n")
}type Graduate struct{Name stringAge intScore int
}func (p *Graduate) ShowInfo() {fmt.Printf("学生姓名=%v 年龄=%v  成绩=%v\n" , (*p).Name, (*p).Age, (*p).Score)
}func (p *Graduate) SetScore(score int) {p.Score = score
}func (p *Graduate) testing(){fmt.Printf("大学数正在考试\n")
}//大学数,研究生。。、func main(){var pupil = &Pupil{Name : "tom",Age : 10,}pupil.testing()pupil.SetScore(90)pupil.ShowInfo()var da = &Graduate{Name : "ada",Age : 20,}da.testing()da.SetScore(50)da.ShowInfo()
}

对上面代码的小结

  1. Pupil 和 Graduate 两个结构体的字段和方法几乎,但是我们却写了相同的代码, 代码复用性不强
  2. 出现代码冗余,而且代码不利于维护,同时也不利于功能的扩展。
  3. 解决方法-通过继承方式来解决

继承基本介绍和示意图

继承可以解决代码复用,让我们的编程更加靠近人类思维。 当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如刚才的Student),在该结构体中定义这些相同的属性和方法。

其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个 Student 匿名结构体即可。 [画 出示意图]
在这里插入图片描述
也就是说:在 Golang 中,如果一个 struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访 问匿名结构体的字段和方法,从而实现了继承特性。

嵌套匿名结构体的基本语法

type Goods struct { Name string Price int
}type Book struct {Goods	//这里就是嵌套匿名结构体 Goods Writer string
}

快速入门案例
我们对 extends01.go 改进,使用嵌套匿名结构体的方式来实现继承特性,请大家注意体会这样编程的好处
在这里插入图片描述

package mainimport ("fmt")type Student struct{Name stringAge intScore int
}func (stu *Student) ShowInfo() {fmt.Printf("学生姓名=%v 年龄=%v  成绩=%v\n" , (*stu).Name, (*stu).Age, (*stu).Score)
}func (stu *Student) SetScore(score int) {stu.Score = score
}//小学生
type Pupil struct{Student//嵌入student匿名结构体
}type Graduate struct{Student
}func (*Pupil) testing(){fmt.Printf("小学生正在考试\n")
}func (p *Graduate) testing(){fmt.Printf("大学数正在考试\n")
}//大学数,研究生。。、func main(){//当嵌入匿名结构体后,使用方法会发生变换pupil := &Pupil{}pupil.Student.Name = "tom~"pupil.Student.Age = 10pupil.testing()pupil.Student.SetScore(70)pupil.Student.ShowInfo()graduate := &Pupil{}graduate.Student.Name = "davade~"graduate.Student.Age = 20graduate.testing()graduate.Student.SetScore(90)graduate.Student.ShowInfo()
}

继承给编程带来的便利

  1. 代码的复用性提高了
  2. 代码的扩展性和维护性提高了

继承的深入讨论

  1. 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法, 都可以使用。【举例说明】
    在这里插入图片描述在这里插入图片描述

  2. 匿名结构体字段访问可以简化,如图
    在这里插入图片描述
    对上面的代码小结
    (1) 当我们直接通过 b 访问字段或方法时,其执行流程如下比如 b.Name
    (2) 编译器会先看 b 对应的类型有没有 Name, 如果有,则直接调用 B 类型的 Name 字段
    (3) 如果没有就去看 B 中嵌入的匿名结构体 A 有没有声明 Name 字段,如果有就调用,如果没有 继续查找…如果都找不到就报错.

  3. 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问 匿名结构体的字段和方法,可以通过匿名结构体名来区分【举例说明】
    在这里插入图片描述

  4. 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身 没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。【举例说明】
    在这里插入图片描述

  5. 如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合 的结构体的字段或方法时,必须带上结构体的名字
    在这里插入图片描述
    在这里插入图片描述

  6. 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

例题:
结构体的匿名字段是基本数据类型,如何访问, 下面代码输出什么
在这里插入图片描述
在这里插入图片描述
说明

  1. 如果一个结构体有 int 类型的匿名字段,就不能第二个。
  2. 如果需要有多个 int 的字段,则必须给 int 字段指定名字

面向对象编程-多重继承

  • 多重继承说明
    如一个 struct 嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方 法,从而实现了多重继承。

  • 案例演示
    通过一个案例来说明多重继承使用
    在这里插入图片描述
    多重继承细节说明

  1. 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来 区分。【案例演示】
    在这里插入图片描述

  2. 为了保证代码的简洁性,建议大家尽量不使用多重继承

接口(interface)

基本介绍

按顺序,我们应该讲解多态,但是在讲解多态前,我们需要讲解接口(interface),因为在 Golang 中 多态 特性主要是通过接口来体现的。

为什么有接口
在这里插入图片描述

接口快速入门

这样的设计需求在 Golang 编程中也是会大量存在的,我曾经说过,一个程序就是一个世界,在现实世 界存在的情况,在程序中也会出现。我们用程序来模拟一下前面的应用场景。

package main import ("fmt"
)//声明/定义一个接口 
type Usb interface {
//声明了两个没有实现的方法Start() Stop()
}type Phone struct {}//让 Phone 实现 Usb 接口的方法
func (p Phone) Start() { fmt.Println("手机开始工作。。。")
}
func (p Phone) Stop() { fmt.Println("手机停止工作。。。")
}type Camera struct {}//让 Camera 实现	Usb 接口的方法
func (c Camera) Start() { fmt.Println("相机开始工作。。。")
}func (c Camera) Stop() { fmt.Println("相机停止工作。。。")
}//计算机
type Computer struct {}//编写一个方法 Working  方法,接收一个 Usb 接口类型变量
//只要是实现了 Usb 接口 (所谓实现 Usb 接口,就是指实现了 Usb 接口声明所有方法)
func (c Computer) Working(usb Usb) { 
//usb 变量会根据传入的实参,来判断到底是 Phone,还是 Camera
//通过 usb 接口变量来调用 Start 和 Stop 方法usb.Start() usb.Stop()
}func main() {//测试//先创建结构体变量 computer := Computer{} phone := Phone{} camera := Camera{}//关键点 computer.Working(phone) computer.Working(camera) 
}

接口概念的再说明

interface 类型可以定义一组方法,但是这些不需要实现。并且 interface 不能包含任何变量。到某个 自定义类型(比如结构体 Phone)要使用的时候,在根据具体情况把这些方法写出来(实现)。

基本语法
在这里插入图片描述
小结说明:

  1. 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的 多态和高内聚低偶合的思想。
  2. Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个 变量就实现这个接口。因此,Golang 中没有 implement 这样的关键字

接口使用的应用场景

在这里插入图片描述在这里插入图片描述

注意事项和细节

  1. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)

在这里插入图片描述

  1. 接口中所有的方法都没有方法体,即都是没有实现的方法。

  2. 在 Golang 中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现 了该接口。

  3. 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型

  4. 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
    在这里插入图片描述
    在这里插入图片描述

  5. 一个自定义类型可以实现多个接口
    在这里插入图片描述

  6. Golang 接口中不能有任何变量
    在这里插入图片描述

  7. 一个接口(比如 A 接口)可以继承多个别的接口(比如 B,C 接口),这时如果要实现 A 接口,也必 须将 B,C 接口的方法也全部实现。
    在这里插入图片描述在这里插入图片描述

  8. interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil

  9. 空接口 interface{} 没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量
    赋给空接口。
    在这里插入图片描述
    在这里插入图片描述

课堂练习
在这里插入图片描述
在这里插入图片描述
实现对 Hero 结构体切片的排序: sort.Sort(data Interface)

package mainimport ("fmt"//"time""math/rand""sort"
)type Hero struct{Name stringAge int
}//Hero结构体切片
type HeroSlice []Hero//实现interface接口
func (hs HeroSlice) Len() int{return len(hs)
}//less方法绝对使用什么标准进行排序
//1.按照年龄进行排序
func (hs HeroSlice) Less(i int,j int) bool{return hs[i].Age < hs[j].Age
}//
func (hs HeroSlice) Swap(i int, j int) {temp := hs[i]hs[i] = hs[j]hs[j] = temp
}//实现对 Hero 结构体切片的排序: sort.Sort(data Interface)func main(){var intslice = []int{0,-1,10,7,98}sort.Ints(intslice)fmt.Println(intslice)//对结构体切片进行排序//测试var heros HeroSlicefor i := 0; i <10; i++{hero := Hero{Name : fmt.Sprintf("英雄~%d", rand.Intn(100)),Age : rand.Intn(100),}heros = append(heros,hero)}for _ , v := range heros{fmt.Println(v)}fmt.Println("排序后")sort.Sort(heros)for _ , v := range heros{fmt.Println(v)}}

实现接口 vs 继承

大家听到现在,可能会对实现接口和继承比较迷茫了, 这个问题,那么他们究竟有什么区别呢
在这里插入图片描述

代码说明:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
对上面代码的小结

  1. 当 A 结构体继承了 B 结构体,那么 A 结构就自动的继承了 B 结构体的字段和方法,并且可以直 接使用
  2. 当 A 结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此我 们可以认为:实现接口是对继承机制的补充.

实现接口可以看作是对 继承的一种补充
在这里插入图片描述

接口和继承解决的解决的问题不同

  • 继承的价值主要在于:解决代码的复用性和可维护性。
  • 接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。

接口比继承更加灵活 Person Student BirdAble LittleMonkey
接口比继承更加灵活,继承是满足 is - a 的关系,而接口只需满足 like - a 的关系。

接口在一定程度上实现代码解耦