1. 接口的基本概念 1.1 接口的定义 1 2 3 4 5 6 7 type Transporter interface { move(src string , dst string ) (int , error ) whistle(int ) int }
1.2 接口的实现 只要结构体拥有接口里声明的所有方法,就称该结构体 实现了接口,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 type Car struct { price int }func (c Car) move(src string , dst string ) (int , error ) { return c.price, nil }func (c Car) whistle(n int ) int { return n }
1.3 接口的本质 接口值由两部分组成,一个指向该接口的具体类型的指针和另外一个指向该具体类型真实数据的指针。
1 2 3 4 5 6 7 8 9 10 11 var tr Transportervar c Car tr = c fmt.Println(tr.whistle(10 )) func foo (a Transporter) { a.whistle(100 ) } foo(c)
1.4 接口的赋值 在实现方法时,可以是值类型的接收者也可以是指针类型的接收者,这两者区别如下:
1 2 3 4 5 6 7 8 func (c Car) whistle(n int ) int { return n } car := Car{}var tr Transporter tr = car 或者 tr = &car
从上面的代码中我们可以发现,使用值接收者实现接口之后,不管是结构体类型还是对应的结构体指针类型的变量都可以赋值给该接口变量。
1 2 3 4 5 6 7 8 9 func (c *Car) whistle(n int ) int { return n } car := Car{}var tr Transporter tr = car tr = &car
2. 接口与类型的关系 2.1 一个类型实现多个接口 1 2 3 4 5 6 7 8 9 type Sayer interface { Say() }type Mover interface { Move() }
Dog
既可以实现Sayer
接口,也可以实现Mover
接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Dog struct { Name string }func (d Dog) Say() { fmt.Printf("%s会叫汪汪汪 " , d.Name) }func (d Dog) Move() { fmt.Printf("%s会动 " , d.Name) }
同一个类型实现不同的接口互相不影响使用。
1 2 3 4 5 6 7 var d = Dog{Name: "旺财" }var s Sayer = dvar m Mover = d s.Say() m.Move()
2.2 多种类型实现同一个接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (d Dog) Move() { fmt.Printf("%s会动" , d.Name) }type Car struct { Brand string }func (c Car) Move() { fmt.Printf("%s速度70迈" , c.Brand) }
这样我们在代码中就可以把狗和汽车当成一个会动的类型来处理,不必关注它们具体是什么,只需要调用它们的Move
方法就可以了。
1 2 3 4 5 6 7 var obj Mover obj = Dog{Name: "旺财" } obj.Move() obj = Car{Brand: "宝马" } obj.Move()
一个接口的所有方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type WashingMachine interface { wash() dry() }type dryer struct {}func (d dryer) dry() { fmt.Println("甩一甩" ) }type haier struct { dryer }func (h haier) wash() { fmt.Println("洗刷刷" ) }
3. 接口嵌套 1 2 3 4 type Steamer interface { Transporter displacement() int }
对于这种由多个接口类型组合形成的新接口类型,同样只需要实现新接口类型中规定的所有方法就算实现了该接口类型。
接口也可以作为结构体的一个字段,我们来看一段Go标准库sort
源码中的示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type Interface interface { Len() int Less(i, j int ) bool Swap(i, j int ) }type reverse struct { Interface }
通过在结构体中嵌入一个接口类型,从而让该结构体类型实现了该接口类型,并且还可以改写该接口的方法。
1 2 3 4 func (r reverse) Less(i, j int ) bool { return r.Interface.Less(j, i) }
Interface
类型原本的Less
方法签名为Less(i, j int) bool
,此处重写为r.Interface.Less(j, i)
,即通过将索引参数交换位置实现反转。
在这个示例中还有一个需要注意的地方是reverse
结构体本身是不可导出的(结构体类型名称首字母小写),sort.go
中通过定义一个可导出的Reverse
函数来让使用者创建reverse
结构体实例。
1 2 3 func Reverse (data Interface) Interface { return &reverse{data} }
这样做的目的是保证得到的reverse
结构体中的Interface
属性一定不为nil
,否者r.Interface.Less(j, i)
就会出现空指针panic。
4. 空接口 空接口是指没有定义任何方法的接口类型。因此任何类型都可以视为实现了空接口。也正是因为空接口类型的这个特性,空接口类型的变量可以存储任意类型的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport "fmt" type Any interface {}type Dog struct {}func main () { var x Any x = "你好" fmt.Printf("type:%T value:%v " , x, x) x = 100 fmt.Printf("type:%T value:%v " , x, x) x = true fmt.Printf("type:%T value:%v " , x, x) x = Dog{} fmt.Printf("type:%T value:%v " , x, x) }
通常我们在使用空接口类型时不必使用type
关键字声明,可以像下面的代码一样直接使用interface{}
。
1 2 3 4 5 func show (a interface {}) { fmt.Printf("type:%T value:%v " , a, a) }
1 2 3 4 5 6 var studentInfo = make (map [string ]interface {}) studentInfo["name" ] = "沙河娜扎" studentInfo["age" ] = 18 studentInfo["married" ] = false fmt.Println(studentInfo)
5. 类型断言 1 2 3 4 5 6 7 func check (i interface {}) { if v, ok := i.(int ); ok { fmt.Println("i is int" , v) } else { fmt.Println("i is not int" , v) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 func justifyType (x interface {}) { switch v := x.(type ) { case string : fmt.Printf("x is a string,value is %v\n" , v) case int : fmt.Printf("x is a int is %v\n" , v) case bool : fmt.Printf("x is a bool is %v\n" , v) default : fmt.Println("unsupport type!" ) } }
只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。切记不要为了使用接口类型而增加不必要的抽象,导致不必要的运行时损耗。
请牢记接口是一种类型,一种抽象的类型。区别于我们在之前提到的那些具体类型(整型、数组、结构体类型等),它是一个只要求实现特定方法的抽象类型。
面的代码可以在程序编译阶段验证某一结构体是否满足特定的接口类型。
1 2 3 4 5 6 type IRouter interface { ... }type RouterGroup struct { ... }var _ IRouter = &RouterGroup{}
6. 面向接口编程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 type payer interface { pay(int ) error }type wc struct {}func (w *wc) pay(int ) error { fmt.Println("wc 10" ) return nil }type zf struct {}func (z *zf) pay(int ) error { fmt.Println("zf 20" ) return nil }func CheckOut (obj payer) { obj.pay(100 ) }func main () { CheckOut(&wc{}) CheckOut(&zf{}) }