一、什么是多态

#接口多态分为两种
1、 多态参数 #我们前面定义一个接口并实例化变量之后
#这个接口变量就可以接收任何实现了该接口的类型

2、 多态数组 #多态数组和参数的区别在于,他定义了一个数组
#这个数组的类型就是接口,他用于存其他实现了这个接口的结构体实例化的变量

案例

package main

import "fmt"

type Usb interface {
Start()
Stop()
}

type Phone struct{}
func (p Phone) Start(){fmt.Println("手机开始工作")}
func (p Phone) Stop(){fmt.Println("手机停止工作")}

type Camera struct{}
func (c Camera) Start() {fmt.Println("相机开始工作")}
func (c Camera) Stop() {fmt.Println("相机停止工作")}




func main(){
var usbArr [3]Usb //这里定义一个Usb接口类型的数组
fmt.Println(usbArr)

usbArr[0] = Phone{} //Phone和Camera结构体已经实现了接口
usbArr[1] = Phone{} //将实例化写入到这个接口宿主中使用
usbArr[2] = Camera{}
fmt.Println(usbArr)

}

返回

[<nil> <nil> <nil>]   //索引位 0、1、2
[{} {} {}] //结构体操作 start stop,这里没给动作所以为空

小结


前面我们已知数组是只能存放相同数据类型的值

这里我们将不同的结构体都存放到数组的接口中,这就是多态数组



二、类型断言


我们接口定义了一些方法的规范,实现了该接口方法的结构体,我们可以通过接口调用

但是,如果我们在结构体里面添加了接口没定义的方法,那么接口该怎么调用呢


 看一段代码

package main

import "fmt"


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)
}

返回

# command-line-arguments
.\main.go:16:4: cannot use a (type interface {}) as type Point in assignment: need type assertion


上面的代码中a = point 将结构体变量赋值给了空接口,我们前面知道空接口是可以接收

任意类型的值的,但是他下面将b = a,又将接口类型的值重新赋值给Point类型是不可用的

不能使用空接口类型进行赋值,如果想要从接口类型转回来需要类型断言操作


 案例

package main

import "fmt"


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.(Point) //类型断言
fmt.Println(b) //{1 2}
}

1、类型断言是怎么实现的


a.(Point)  类型断言,他会判断a变量是否指向了Point类型的变量

如果是就转换成Point类型,并赋值给b变量,否则报错

(如果想将空接口转换为特定的结构体,就可以使用类型断言)


 错误案例

package main

import "fmt"

func main(){
var t float32
var x interface{}
x = t
y := x.(float64) //原先t是float32类型,我们这里故意转换为float64类型
fmt.Println(y)
}

返回

panic: interface conversion: interface {} is float32, not float64


在进行类型断言时,如果类型不匹配,就会报panic

因此进行类型断言时,要确保原来的空接口指向的就是要断言的类型

我们为了防止panic的出现,这里做一个检测机制


package main

import "fmt"

func main(){
var t float32
var x interface{}
x = t
y,err := x.(float64) //在断言时传入的只有两个
//一个是具体的数值,一个是状态值err 通过变量接收

if err { //判断err 是否为true,判断上面转换是否成功
fmt.Println("转换成功")
} else{
fmt.Println("转换失败")
}

fmt.Println("继续执行",y)

}


在进行断言时,带上检测机制,如果成功就OK,失败了也不要报panic
panic会使得程序崩溃,后续代码无法执行


另一种写法

package main

import "fmt"

func main(){
var x interface{}
var t float32
x = t

//直接把类型断言做成判断
if y,err := x.(float64); err {
fmt.Println("转换成功")
fmt.Println(y)
} else{
fmt.Println("转换失败")
}
fmt.Println("继续执行")
}

类型断言最佳实践1


在前面的Usb接口案例做改进,给Phone结构体添加一个特有的方法call

当Usb接口接收的是Phone结构体变量时,还需要调用call方法



package main

import "fmt"

type Usb interface {
Start()
Stop()
}

type Phone struct{name string}
func (p Phone) Start(){fmt.Println("手机开始工作")}
func (p Phone) Stop(){fmt.Println("手机停止工作")}


type Camera struct{}
func (c Camera) Start() {fmt.Println("相机开始工作")}
func (c Camera) Stop() {fmt.Println("相机停止工作")}




type Computer struct{}

func (computer Computer) Working(usb Usb) {
usb.Start()
usb.Stop()
}

func main(){

var usbArr [3]Usb
usbArr[0] = Phone{"vivo"}
usbArr[1] = Phone{"小米"}
usbArr[2] = Phone{"尼康"}



var computer Computer
for _,value := range usbArr {
computer.Working(value)
}
}


我们要做的是,当调用Working方法时,如果发现传入到接口中的是Phone结构体变量时

我们会单独调用一下Phone中特有的Call方法(我们自己创建)

但是我们无法直接去指定usb.Call   因为接口中没有指定这个规范

我们可以依照类型断言去判断接口中的值,是否是Phone类型

如果是则调用Phone下的call方法


package main

import "fmt"

type Usb interface {
Start()
Stop()
}

type Phone struct{name string}
func (p Phone) Start(){fmt.Println("手机开始工作")}
func (p Phone) Stop(){fmt.Println("手机停止工作")}

//给Phone添加一个Call方法,这个方法接口是没有的,是Phone特有的方法
func (p Phone) Call(){fmt.Println("手机炸了")}


type Camera struct{}
func (c Camera) Start() {fmt.Println("相机开始工作")}
func (c Camera) Stop() {fmt.Println("相机停止工作")}

type Computer struct{

}
func (computer Computer) Working(usb Usb) {
usb.Start()

if phone,err := usb.(Phone); err == true { //类型断言,怕判断是否是Phone结构体类型
phone.Call()
}

usb.Stop()
}

func main(){

var usbArr [3]Usb
usbArr[0] = Phone{"vivo"}
usbArr[1] = Phone{"小米"}
usbArr[2] = Phone{"尼康"}


//Phone还有一个特有的方法call,请遍历Usb数组,如果是Phone变量
//除了调用Usb接口声明的方法外,还需要调用Phone特有的方法call
var computer Computer
for _,value := range usbArr {
computer.Working(value)
}
}

类型断言最佳实践2


写一个函数,循环判断传入参数的类型



package main

import "fmt"

func TypeJudge(items... interface{}){
//items... 可变参数 可以接收多个值放在items中
//这里是空接口,可以接收任意类型的实参
for index,x := range items{
switch x.(type) {
case bool:
fmt.Printf("第%v个参数是bool类型, 值是%v\n",index,x)
case float32:
fmt.Printf("第%v个参数是float32类型, 值是%v\n",index,x)
case float64:
fmt.Printf("第%v个参数是float64类型, 值是%v\n",index,x)
case int,int32,int64:
fmt.Printf("第%v个参数是整数类型, 值是%v\n",index,x)
case string:
fmt.Printf("第%v个参数是string类型, 值是%v\n",index,x)
default:
fmt.Printf("第%v个参数是 不确定类型, 值是%v\n",index,x)
}
}
}

func main(){
var n1 float32 = 1.1
var n2 float64 = 2.2
var n3 int32 = 30
var name string = "tom"
address := "北京"
n4 := 300

//上面设置多个字符
TypeJudge(n1,n2,n3,name,address,n4)
}

返回

第0个参数是float32类型, 值是1.1
第1个参数是float64类型, 值是2.2
第2个参数是整数类型, 值是30
第3个参数是string类型, 值是tom
第4个参数是string类型, 值是北京
第5个参数是整数类型, 值是300

类型断言最佳实践3


在上面的案例的基础上添加了判断是否是结构体


package main

import "fmt"


//添加一个结构体
type Student struct{}


func TypeJudge(items... interface{}){
for index,x := range items{
switch x.(type) {
case bool:
fmt.Printf("第%v个参数是bool类型, 值是%v\n",index,x)
case float32:
fmt.Printf("第%v个参数是float32类型, 值是%v\n",index,x)
case float64:
fmt.Printf("第%v个参数是float64类型, 值是%v\n",index,x)
case int,int32,int64:
fmt.Printf("第%v个参数是整数类型, 值是%v\n",index,x)
case string:
fmt.Printf("第%v个参数是string类型, 值是%v\n",index,x)

//在case中添加对比Student类型即可
case Student:
fmt.Printf("第%v个参数是Student类型, 值是%v\n",index,x)
case *Student:
fmt.Printf("第%v个参数是*Student类型, 值是%v\n",index,x)

default:
fmt.Printf("第%v个参数是 不确定类型, 值是%v\n",index,x)
}
}
}



func main(){
var n1 float32 = 1.1
var n2 float64 = 2.2
var n3 int32 = 30
var name string = "tom"
address := "北京"
n4 := 300

//声明结构体
stu1 := Student{}
stu2 := &Student{}


//添加stu变量
TypeJudge(n1,n2,n3,name,address,n4,stu1,stu2)
}