前言
按理来说,OOP编程的三大特性,前面讲完了两个,应该下一个就是多态,但是Golang中 的多态就是通过接口来实现的,所以我们先讲一下接口。
接口(interface)
接口的设计在Golang中是大量存在的,Golang面向对象的核心就是接口编程,Golang与其他的语言相比也是很有特色。
快速入门
我们这里不讲细节,就感受一下,接口是怎么用的
Go 不需要 implements 关键字。只要你的类型有接口中定义的所有方法,就算实现了该接口。
package main
import "fmt"
type Speaker interface {
Speak() string
}
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return "Woof! My name is " + d.Name
}
type Cat struct {
Name string
}
func (c Cat) Speak() string {
return "Meow! I'm " + c.Name
}
func main() {
var s Speaker
s = Dog{Name: "Buddy"}
fmt.Println(s.Speak())
s = Cat{Name: "Luna"}
fmt.Println(s.Speak())
}

注意:Dog 和 Cat 并没有“声明”自己实现了 Speaker,但因为它们都有 Speak() string 方法,所以自动满足接口,没理解我们来形象的理解一下。
| 代码 | 比喻 | 说明 |
|---|---|---|
type Speaker interface { Speak() string } | 规则:“必须会说话” | 定义能力标准 |
func (d Dog) Speak() ... | 狗狗学会了自我介绍 | 实现接口的方法 |
func (c Cat) Speak() ... | 猫咪也会自我介绍 | 另一个实现 |
var s Speaker | 一个通用话筒 | 只接受“会说话的” |
s = Dog{...} / s = Cat{...} | 换不同动物上台 | 多态:同一个话筒,不同表现 |
基本介绍
interface类型可以定义一组方法,但是不需要实现,Interface不能包含任何变量,到某个自定义类型要使用的时候,根据具体情况把这些方法写出来,语法就是
type 接口名 interface {
方法() 返回值列表
方法() 返回值列表
}
小结:
1.里面的所有放啊都是没有方法体的,即所有的发给发都没有实现的方法,接口体现了程序设计的多态,和高内聚低耦合的思想
2.Golang 的接口,不需要显式的实现,只需要一个变量,含有接口类型中的所有方法,那么这个变量就是先这个接口,在Golang中没有implements 关键字
注意事项和细节说明
1.接口本身是不能创建实例的,但是可以指向一个实现接口的自定义类型的变量。
type AInterface interface {
Say()
}
type Stu struct {
Name string
}
func (stu Stu) Say() {
fmt.Println("Stu say")
}
func main() {
var stu Stu
var a AInterface = stu
a.Say()
}

但是如果这个自定义类型没有实现这个接口的所有方法,就不能只想这个自定义类型的变量。
2.一个自定义类型实现了某个接口的所有方法,我们说这个自定义类型实现了该接口,一个自定义类型只有实现了某个接口,才能将该自定义类型的变量赋给该接口类型
3.只要是自定义数据类型就可以实现接口,不一定是结构体
4.一个自定义类型可以实现多个接口
type AInterface interface {
Say()
}
type BInterface interface {
Hello()
}
type Stu struct {
Name string
}
func (stu Stu) Say() {
fmt.Println("Stu say")
}
func (stu Stu) Hello() {
fmt.Println("Stu Hello")
}
func main() {
var stu Stu
var a AInterface = stu
var b BInterface = stu
a.Say()
b.Hello()
}
5.接口不能有任何变量
6.一个接口可以继承多个别的接口,这个时候如果要实现这个接口也必须把继承的接口的方法全部实现
package main
import "fmt"
type AInterface interface {
Say()
}
type BInterface interface {
Hello()
}
type CInterface interface {
AInterface
BInterface
World()
}
type Stu struct {
Name string
}
func (stu Stu) Say() {
fmt.Println("Stu say")
}
func (stu Stu) Hello() {
fmt.Println("Stu Hello")
}
func (stu Stu) World() {
fmt.Println("Stu World")
}
func main() {
var stu Stu
var c CInterface = stu
c.Say()
c.Hello()
c.World()
}

7.Interface类型默认是引用类型(指针),如果没有初始化就是使用就会输出nil
8.空接口没有任何方法,所以所有类型都实现了空接口,我们可以把任何一个变量赋给空接口。
type T interface {
}
小练习
需求:对Hero结构体切片的排序
思路:对数组/切片排序,我们当然可以用冒泡排序,也可以用系统提供的一些方法sort package – sort – Go Packages,对结构体排序我们也可以用冒泡,也可以用系统提供的函数sort package – sort – Go Packages。

我们可以看到,如果我们要用这个接口,我们就要实现这三个方法。
我们来写一些看看
package main
import (
"fmt"
"math/rand"
"sort"
)
type Hero struct {
Name string
Age int
}
type Heroslice []Hero
func (hs Heroslice) Len() int {
return len(hs)
}
func (hs Heroslice) Less(i, j int) bool {
return hs[i].Age < hs[j].Age
}
func (hs Heroslice) Swap(i, j int) {
temp := hs[i]
hs[i] = hs[j]
hs[j] = temp
}
func main() {
var intSli = []int{5, 10, 8, 520, 666, 68, 0}
sort.Ints(intSli)
fmt.Println(intSli)
/*测试是否可以对结构体切片进行排序*/
var heroes Heroslice
for i := 0; i < 10; i++ {
hero := Hero{
Name: fmt.Sprintf("英雄%d", rand.Intn(1000)),
Age: rand.Intn(100),
}
heroes = append(heroes, hero)
}
for _, v := range heroes {
fmt.Println(v)
}
sort.Sort(heroes)
fmt.Println("---------------------------------------------")
for _, v := range heroes {
fmt.Println(v)
}
}
接口和继承
package main
import "fmt"
type Monkey struct {
Name string
}
func (this *Monkey) climbing() {
fmt.Println(this.Name, "生来会爬树")
}
type BabyMonkey struct {
Monkey
}
func main() {
/*创建一个实例*/
monkey := BabyMonkey{
Monkey{
Name: "孙悟空",
},
}
monkey.climbing()
}
这里BabyMonkey继承了猴子结构体的可以爬树的方法,但是BabyMonkey是孙悟空,他还可以腾云驾雾,可以像鸟一样飞,我们可以用接口来实现
package main
import "fmt"
type Monkey struct {
Name string
}
type BabyMonkey struct {
Monkey
}
type BirdAble interface {
Flying()
}
func (this *Monkey) climbing() {
fmt.Println(this.Name, "生来会爬树")
}
func (this *BabyMonkey) Flying() {
fmt.Println(this.Name, "学会了飞")
}
func letItFly(b BirdAble) {
b.Flying()
}
func main() {
/*创建一个实例*/
monkey := BabyMonkey{
Monkey{
Name: "孙悟空",
},
}
monkey.climbing()
letItFly(&monkey)
}
接口可以作为结构体的功能的一种拓展,而且不会破坏结构体原本的继承关系。
这里因为比较简单,所以说可能并不能太能理解到接口方便的点。
继承的价值在于解决代码的复用性和可维护性,接口的价值在于设计好各种规范让其自定义类型去实现这些方法,接口比继承更加灵活,继承是满足is –a 的关系,接口是需要满足like –a的关系,接口在一定程度上实现代码解耦。
多态
基本介绍
变量/实例具有多种形态,是面向对象的第三大特征,在Go语言中多态是通过接口来实现的,可以按照统一的接口调用不同的实现,这个时候接口变量就呈现不同的形态
快速入门
package main
import "fmt"
type Speaker interface {
Speak() string
}
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return "Woof! My name is " + d.Name
}
type Cat struct {
Name string
}
func (c Cat) Speak() string {
return "Meow! I'm " + c.Name
}
func main() {
var s Speaker
s = Dog{Name: "Buddy"}
fmt.Println(s.Speak())
s = Cat{Name: "Luna"}
fmt.Println(s.Speak())
}
在上面已经实现过一遍的案例中,Speaker的接口既可以接受Dog的变量又可以接受Cat的变量,就体现了Speaker多态的特性,这种体现被称为多态参数,还有一种体现方式叫做多态数组。
我们来看一个案例,我们给一个Speakers数组里存放Dogs和Cats结构体
package main
import "fmt"
type Speaker interface {
Speak() string
}
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return "Woof! My name is " + d.Name
}
type Cat struct {
Name string
}
func (c Cat) Speak() string {
return "Meow! I'm " + c.Name
}
func main() {
var speakers [3]Speaker
speakers[0] = Dog{"111"}
speakers[1] = Cat{"222"}
speakers[2] = Dog{"333"}
fmt.Println(speakers)
}

?????这对吗,这不对吧,怎么能够放两个不同类型的结构体进去,我勒个豆
这就是多态的魅力。
类型断言
这个时候,Cat除了调用Speaker接口声明的方法外,还有一个特有的方法叫Backflip,请遍历数组,如果是Cat变量,那就除了实现接口声明的变量外,还要实现Backflip。
这里我们就需要一种判断,判断这里的Speaker是哪个类型,这里我就就需要用到类型断言
我们先来看一段错误代码
type Point struct {
x int
y int
}
func main() {
var a interface{}
var point Point = Point{1, 2}
a = point
var b Point
b = a /*❌ 编译报错*/
fmt.Println(b)
}
- a 的静态类型是 interface{},编译器只知道:里面“可能装着任何东西”。
- b 的类型是 Point。
- 直接赋值要求:右边的静态类型要能赋值给左边(编译期就得确定)。
- 编译器并不知道 a 里面一定是 Point,所以必须你“声明一下类型”——也就是类型断言。
我们这里用直接断言
b := a.(Point) // 这里运行时会检查:a 里是不是 Point
我们就能正确运行,但是这样不够安全,如果类型不对,会报panic,我们通常会加一个判断
b, ok := a.(Point)
if !ok {
fmt.Println("a 里装的不是 Point")
}else{
fmt.Println(b)
}
我们再回过头来实现最开始的需求
Cat除了调用Speaker接口声明的方法外,还有一个特有的方法叫Backflip,请遍历数组,如果是Cat变量,那就除了实现接口声明的变量外,还要实现Backflip。
package main
import "fmt"
type Speaker interface {
Speak() string
}
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return "Woof! My name is " + d.Name
}
type Cat struct {
Name string
}
func (c Cat) Speak() string {
return "Meow! I'm " + c.Name
}
func (c Cat) Backflip()string{
return "Backfliping"
}
type Animals struct{
}
func (Animals) Doing(speaker Speaker) string {
result := speaker.Speak()
if cat, ok := speaker.(Cat); ok {
result += " " + cat.Backflip()
}
return result
}
func main() {
var speakers [3]Speaker
speakers[0] = Dog{"111"}
speakers[1] = Cat{"222"}
speakers[2] = Dog{"333"}
fmt.Println(speakers)
var animals Animals
for _,v := range speakers{
var str string
str = animals.Doing(v)
fmt.Println(str)
}
}
我们再来一个小案例,写入一个函数通过循环判断传入参数的类型
func TypeJudge(items ...interface{}) {
for i, x := range items {
switch x.(type) { // 这里 type 是一个关键字,固定写法
case bool:
fmt.Printf("param #%d is a bool 值是%v\n", i, x)
case float64:
fmt.Printf("param #%d is a float64 值是%v\n", i, x)
case int, int64:
fmt.Printf("param #%d is an int 值是%v \n", i, x)
case nil:
fmt.Printf("param #%d is nil 值是%v \n", i, x)
case string:
fmt.Printf("param #%d is a string 值是%v\n", i, x)
default:
fmt.Printf("param #%d's type is unknown , 值是%v\n", i, x)
}
}
}
严格说:这段代码用的是 类型 switch,不是那种 x.(T) 形式的“显式类型断言”。
原理上来看:类型 switch 是基于类型断言实现的语法糖,它在内部帮你对 x 做了一连串的类型断言,只是你不用自己一条条写 x.(int), x.(string) 了









