一.知识回顾
在 Go 语言中,接口是一种强大的抽象机制,它允许我们定义一组方法签名,任何类型只要实现了这些方法,就被视为实现了该接口。接口的实现是隐式的,这意味着类型不需要显式声明它实现了某个接口,只要提供了接口所需的所有方法即可。
接口:就是一组方法的签名,任何类型只要实现这些方法,就可以被视为实现了该接口
package main
import "fmt"
type speaker interface {
speak()//只要实现了speak方法的,都可以是speaker类型
}
type cat struct{}
type dog struct{}
func (d dog) speak() {
fmt.Println("汪汪汪")
}
func (c cat) speak() {
fmt.Println("喵喵喵")
}
func da(x speaker) {
//接收参数,传入什么,我就打印什么
x.speak()
}
// 在编程存在
func main() {
var c1 cat
var d1 dog
da(c1)
da(d1)
}
所以接口是比较抽象的东西,他的这个类型只是存储了共同的方法,这样的目的就是为了实现不管传入是什么类型的,都可以实现他的方法
1.1 接口的实现和实现
type 接口名 interfacce{
方法名1(参数1,参数2....)(返回值1,返回值2....)
方法2.....
}
接口的实现:
一个变量如果实现了接口中规定的所有的方法,那么这个变量就实现了这个接口,可以称为这个接口类型的变量。
(这一句话很重要,你要实现了所有的方法才算是说是接口,如果你只是实现了接口内所定义的个别方法的话,就不能称之为接口)
就比如猫狗,这些就是接口类型,也就是speaker类型。
如果一个接口没有方法呢?
这样的接口被称为是空接口
它的主要作用:
- 作为函数的参数(可以是任意类型,因为所有类型都实现了空接口)
- 作为map的值,可以是字典保存任意类型的值
正是因为有了空接口的概念,就会出现分不清1和 '1'的情况,这个时候就需要类型断言
package main
import "fmt"
func assign(a interface{}) {
str, ok := a.(string)
if ok {
fmt.Println(str)
} else {
fmt.Println("错误")
}
//fmt.Printf("type:%T,value:%v\n", a, a)
//这里其实用switch猜合适
}
func assign2(a interface{}) {
fmt.Printf("type:%T,value:%v\n", a, a)
switch a.(type) {
case int:
fmt.Println("int")
case string:
fmt.Println("string")
case bool:
fmt.Println("bool")
case float64:
fmt.Println("float64")
}
}
func main() {
var m1 map[string]interface{}
m1 = make(map[string]interface{}, 16)
m1["name"] = "cxy"
m1["age"] = 18
m1["married"] = true
m1["hobby"] = [...]string{"唱", "跳"}
fmt.Println(m1)
assign2("100")
}
1.2 接口和类型的关系
多个类型可以实现一个接口,是上述我们经常使用的。
除此之外还可以一个类型对应多个接口
1.3 接口的嵌套
接口的嵌套,也就是接口里包含接口,要实现的话,父接口,就必须实现所有的子接口才可以。
二.接口实现面对对象的三大特征
面对对象的3个概念:封装,继承,多态
2.1 封装
封装的好处:
- 隐藏实现细节;
- 可以对数据进行校验,保证安全合理;
go语言的封装和其他语言稍微有点不同。
主要就是go语言的封装是包级别的,在同一个包内均是可见的,其它包内是不可见的,除非是大写字母开头才是可见的。否则只可以通过封装来实现。
接下来写一个封装的例子:
package main
import "fmt"
type Person struct {
name string
age int
}
func (p *Person) SetName(name string) {
p.name = name
}
func (p *Person) SetAge(age int) {
p.age = age
}
func (p *Person) GetName() string {
return p.name
}
func (p *Person) GetAge() int {
return p.age
}
func main() {
//p := &Person{}
p := new(Person)
p.SetName("Alice")
p.SetAge(18)
fmt.Println(p.GetName()) //Alice
fmt.Println(p.GetAge()) //18
}
2.2 继承
go里面的继承可能和之前学习的不太一样,但是大致内容是一致的,只是他的形式不太一样而已。
这里我们会发现一个问题:
就是结构体的嵌套和继承是有区别的,要注意
在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,这就是所谓的继承。
package main
import "fmt"
type animal struct {
name string
}
func (a animal) wang() {
fmt.Printf("%d会动", a.name)
}
type dog struct {
animal //这样的话animal内有的属性,这样的话他的后代dog也会有name属性,
feet int
}
func (d dog) wang() {
fmt.Printf("%s会汪汪汪", d.name)
//这里的查找如果dog没有的话就会找上一级结构体。
}
// 返回尽量使用指针,如果返回变量的话开销比较大
func Init(name string) *dog {
return &dog{
animal: animal{
name: name,
},
feet: 4,
}
}
func main() {
a1 := Init("haha\n")
a1.wang()
fmt.Println(a1)
}
package main
import "fmt"
type animal struct {
name string
}
func (a animal) wang() {
fmt.Printf("%d会动", a.name)
}
type dog struct {
animal
feet int
}
func (d dog) wang() {
fmt.Printf("%s会汪汪汪\n", d.name)
//这里的查找如果dog没有的话就会找上一级结构体。
}
func (a *dog) Init(name string, feet int) {
a.name = name
a.feet = feet
}
func main() {
a1 := &dog{}
a1.Init("小狗", 4)
a1.wang()
fmt.Println(a1.name)
fmt.Println(a1.feet)
}
小狗会汪汪汪
小狗
4
而结构体的嵌套是什么样子呢?举一个简单的例子

就是加上类型。
继承存在的问题是非常多的,在后续都会介绍。
2.3 多态
多态啥意思?
就是在不同继承关系的类对象,调用同一函数时,产生的不同行为。
暂时先这样理解
变量(实例)具有多种形态,在Go语言中,多态特征是通过接口实现的。
可以按照统一的接口来调用不同的接口实现。这时接口变量就呈现出不同的形态。
这就是go里面多态的实现。
package main
import "fmt"
type Speaker interface {
Speak()
}
type Person struct {
name string
}
func (p *Person) Speak() {
fmt.Println("Hello, I am a person")
}
type Cat struct {
name string
}
func (c *Cat) Speak() {
fmt.Println("Hello, I am a cat")
}
func main() {
speaker1 := &Person{}
speaker2 := &Cat{}
speaker1.Speak()
speaker2.Speak()
}
Hello, I am a person
Hello, I am a cat
虽然这里也实现了,但是实际上,不能完全被称为多态,为什么?
因为这里没有做到父类指针(或者父类引用)指向子类对象
func main() {
var speaker Speaker
speaker = &Cat{}
speaker.Speak()
speaker = &Person{}
speaker.Speak()
}
三.多继承和二义性
3.1 多继承
多继承:就是指一个结构体里面有着多个匿名结构体,从而实现了一个结构体的多个继承
那么随之而来就会有很多问题:
比如:如果我继承的多个父类,他们(包括本身)有相同的字段怎么办呢?
这里go语言提供了一种解决办法,就是通过加匿名字段的类型,从而实现对相同属性的区分
(这里也被称之为同名二义性)
并且继承之后,也可以调用父类的方法,不过虽然是调用父类的方法,但是本质你还是修饰的父类,所以打印的内容还是属于是父类的。
package main
import "fmt"
type Father struct {
Name string
Age int
}
type Mother struct {
Name string
}
type Son struct {
Name string
// 结构体的匿名字段可以是基础数据类型,这种没有名字的字段就称为匿名字段,调用时基于该基础数据类型调用即可;
//这里匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名;
// 结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。
// 继承多个结构体,尽管Go语言支持多继承,但推荐少用。很多语言就将多重继承去除了,因为容易造成逻辑混乱。
Father
Mother
}
func (f *Father) DriveCar() {
fmt.Printf("%s开车很稳~\n", f.Name)
}
func (m *Mother) Sing() {
fmt.Printf("%s唱歌很好听~\n", m.Name)
}
func (s *Son) Dance() {
fmt.Printf("%s跳舞很好看\n", s.Name)
}
func main() {
// 构建Son结构体实例
// s := Son{"唐三", 18, Father{"唐昊", 30}, Mother{"阿银"}}
s := Son{
"唐三",
18,
// 在创建嵌套匿名结构体变量(实例)时,可以直接指定各个匿名结构体字段的值;
Father{
Name: "唐昊",
Age: 30,
},
Mother{
Name: "阿银",
},
}
fmt.Printf("s = %v\n", s)
// 通过Son结构体实例的确可以调用多个继承结构体的方法
s.Sing()
s.Dance()
s.DriveCar()
// 如嵌入的匿名结构体有相同的字段名或者方法名称,则在访问时,需要通过匿名结构体类型名来区分;
fmt.Printf("[%s]的今年[%d]岁, 父亲是: [%s], 今年[%d]岁, 母亲是: [%s]\n", s.Name, s.int, s.Father.Name, s.Age, s.Mother.Name)
}
s = {唐三 18 {唐昊 30} {阿银}}
阿银唱歌很好听~
唐三跳舞很好看
唐昊开车很稳~
[唐三]的今年[18]岁, 父亲是: [唐昊], 今年[30]岁, 母亲是: [阿银]
3.2 二义性
二义性:主要分为同名二义性和路径二义性
同名二义性就是字段名字一样,通过加上匿名字段来分隔就可以
其次就是路径二义性:举个例子:

子类调用祖父类的属性,父类有两个,编译器不知道这个属性是从哪一个路径来的,被称为是路径二义性。
package main
import "fmt"
type A struct {
X int
}
type B struct {
A
}
type C struct {
A
}
type D struct {
B
C
}
func main() {
test := D{B: B{A{X: 1}}, C: C{A{X: 2}}}
fmt.Println(test.X)
//fmt.Println(test.B.X) // 输出: 1
//fmt.Println(test.C.X) // 输出: 2
}

对于这种情况,就会出现一个报错的问题。
至于怎么解决,其实和解决上述方法一样,加上前缀匿名字段即可

四.多态的进一步了解
4.1 多态实现的三个条件
多态:总结一句话:同一对象的不同表现形式
在C++的学习里面,常说多态有三个条件嘛:
- 要有继承
- 要有虚函数重写
- 父类指针(或者父类引用)指向子类对象
但是在go语言稍微有点区别,其实继承已经改为实现接口,虚函数重写其实就是类似一个接口,通过对接口定义的方法进行重写,最后通过接口对象获取不同的类型,从而达到一个多态的效果。
package main
import "fmt"
type Speaker interface {
Speak()
}
type Person struct {
name string
}
func (p *Person) Speak() {
fmt.Println("Hello, I am a person")
}
type Cat struct {
name string
}
func (c *Cat) Speak() {
fmt.Println("Hello, I am a cat")
}
func main() {
var speaker Speaker
speaker = &Cat{}
speaker.Speak()
speaker = &Person{}
speaker.Speak()
}