1.接口的定义
定义一个接口往往是从它所需要的函数为入口的,说人话就是【规定几个函数放在一起形成新整体】
比如:我有三个函数,gaga(),walk(),swimming(),那就可以规定它们3个放一起形成一个接口
// Duck 定义接口
// 接口典型的一个特点就是,多个功能【函数】的集合
type Duck interface {
gaga()
walk()
swimming()
}
2.不得不提到的鸭子类型
这个概念的名字来源于James Whitcomb Riley提出的鸭子测试:
当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
从这句著名的话中,我们可以看出,符合【鸭子走】【鸭子游】【鸭子叫】的鸟可以叫它鸭子
所以反映的就是,【鸭子走】【鸭子游】【鸭子叫】3个功能形成了 Duck 这个接口
【那具有功能】这个意思,用代码怎么体现呢,答案是,给结构体绑定函数
package main
import "fmt"
// Duck 定义接口
type Duck interface {
gaga()
walk()
swimming()
}
type bird struct{}
func (b *bird) gaga() {
fmt.Println("ga ga ga")
}
func (b *bird) walk() {
fmt.Println("像鸭子走路")
}
func (b *bird) swimming() {
fmt.Println("像鸭子游泳")
}
func main() {
//鸭子类型【接口】,指的是你的结构体只要绑定了鸭子类型【接口】的所有方法,那么就可以说你这个结构体是鸭子类型【接口】
//所以按照这个理论,要是鸭子类型【里面没有方法】,那么所有的结构体就都可以说自己是一个鸭子类型【接口】
//从而一切结构体都能赋值给空接口
//bird结构体是绑定了三个方法的,所以就可以说bird是一个Duck类型【接口】
var d Duck = &bird{} //结构体指针
d.walk()
d.gaga()
d.swimming()
}
3.结构体 与 接口的多对多关系
一个结构体可以实现多个接口,多个结构体也可以对应一个接口
type powerDuck interface {
charge() //可以充电的是电动鸭子
}
type rubberDuck interface {
swimming() //可以游泳的是橡皮鸭子
}
// redDuck绑定了,charge + swimming两个方法
// 所以redDuck既可以说它是电动鸭子,也可以说它是橡皮鸭子
// 这就是一个结构体实现了多个鸭子类型【接口】
type redDuck struct{}
func (r *redDuck) charge() {
fmt.Println("红色鸭子可以充电,所以它是电动鸭子")
}
func (r *redDuck) swimming() {
fmt.Println("红色鸭子可以游泳,所以红色鸭子是橡皮鸭子")
}
type yellowDuck struct{}
// yellowDuck也实现了powerDuck接口
// 所以加上redDuck,这就是多个结构体实现了同一个powerDuck鸭子类型【接口】
func (y *yellowDuck) charge() {
fmt.Println("黄色鸭子可以充电,所以它是电动鸭子")
}
4.接口嵌套
package main
import "fmt"
type powerDuck interface {
charge() //可以充电的是电动鸭子
}
type rubberDuck interface {
swimming() //可以游泳的是橡皮鸭子
}
type supperDuck interface {
powerDuck
rubberDuck
fly()
}
type redDuck struct{}
func (r *redDuck) charge() {
fmt.Println("红色鸭子可以充电,所以它是电动鸭子")
}
func (r *redDuck) swimming() {
fmt.Println("红色鸭子可以游泳,所以红色鸭子是橡皮鸭子")
}
func (r *redDuck) fly() {
fmt.Println("红色鸭子可以飞,所以红色鸭子是飞行鸭子")
}
func main() {
//有一个问题,各个函数前面都绑定了指针,那可不可以绑定普通结构体变量呢??
//答案是可以:区别是,绑定指针,那么下面的s赋值就必须是 &redDuck{}
//如果绑定普通结构体变量,那么下面的s赋值就是redDuck{} 或者 &redDuck{}
//这里的本质就是值传递和指针传递,值传递会用副本,而指针就是源数据
var s supperDuck = &redDuck{}
s.charge()
s.swimming()
s.fly()
}
5.结构体嵌套接口变量【实现解耦效果】
package main
import "fmt"
type duck interface {
gaga()
walk()
}
type chicken struct {
duck
}
func (b *chicken) gaga() {
fmt.Println("chicken gaga")
}
type bird struct{}
func (s *bird) gaga() {
fmt.Println("bird gaga")
}
func (s *bird) walk() {
fmt.Println("bird walk")
}
type goose struct{}
func (g *goose) gaga() {
fmt.Println("goose gaga")
}
func (g *goose) walk() {
fmt.Println("goose walk")
}
func main() {
c := chicken{duck: &bird{}}
cc := chicken{duck: &goose{}}
c.gaga() //chicken原本只绑定了gaga()
//由于chicken肚子里面嵌套了duck接口,所以它可以通过实现了duck接口的任何结构体调walk
//并且传进来的结构体不一样,walk出来也不一样===这就是解耦
c.duck.gaga()
c.duck.walk() //相当于c.walk()
cc.duck.gaga()
cc.duck.walk() //相当于cc.walk()
}
//chicken gaga
//bird gaga
//bird walk
//goose gaga
//goose walk
6.给万能的接口切片赋值也有坑
package main
import "fmt"
// MyPrint :我们在用别人的库的时候,经常遇到下面这种函数
// 就是可以给【入参】传任意值的函数,data就相当于一个interface的切片
func MyPrint(data ...interface{}) {
for _, v := range data {
fmt.Println(v)
}
}
func main() {
//那么真的是可以传万物吗??
//data1就是一个接口切片,里面可以放万物的
data1 := []interface{}{
"xhz", "hello", 100,
}
MyPrint(data1...)
data2 := []string{
"a", "b", "c",
}
//MyPrint(data2...)发现编辑就会报错,不过是把interface特殊化成了string吗
//结果就不支持传数据了,这和我们理解的【装万物】有点不一样啊
//怎样解决呢??思路是,既然支持interface切片,那就提前把string切片的数据拿出来放在interface切片里
var data3 []interface{}
for _, v := range data2 {
data3 = append(data3, v)
}
MyPrint(data3...)
}