接口作用
Go
语言中的接口是一种类型,类似于Python
中的抽象基类。
Go
语言中使用接口来体现多态,是duck-type
的一种体现。
如,只要一个东西会叫,会走,那么我们就可以将它定义为一个动物的接口。
接口定义
Go
中提倡面向接口编程,以下是接口的定义。
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
关于接口的定义有以下几点注意事项:
接口名在单词后面一般都需添加er的后缀,代表这是一个接口
当接口名与方法名都是大写时,代表该接口与方法均可被外部包进行访问
参数列表以及返回值列表参数变量名可以省略
以下我们将定义一个动物的接口,会叫,会移动我们将将它看作为动物。
并且我们为该接口定义了一个方法撒泼,只要是动物就可以进行撒泼。
package main
import (
"fmt"
)
type animal interface {
move()
roar()
}
func sapo(a animal){ // 接收一个动物类型
a.move()
a.roar()
}
接口使用
如何使用上面的接口呢?其实我们还需要做结构体。
如下示例,做了一个狗的结构体和一个狼的结构体并且进行实例化,由于狗和狼都实现了move
以及roar
方法,所以这两个实例化都可以看作是animal
类型,即可以调用sapo
方法。
package main
import (
"fmt"
)
type animal interface {
move()
roar()
}
func sapo(a animal){
a.move()
a.roar()
}
type dog struct {
name string
}
func (d dog) move() {
fmt.Printf("%s在移动\n", d.name)
}
func (d dog) roar() {
fmt.Printf("%s在吼叫\n", d.name)
}
type wolf struct {
name string
}
func (w wolf) move() {
fmt.Printf("%s在移动\n", w.name)
}
func (w wolf) roar() {
fmt.Printf("%s在吼叫\n", w.name)
}
func main() {
d1 := dog{
name: "大黄",
}
w1 := wolf{
name: "灰太狼",
}
sapo(d1) // 大黄调用动物的撒泼方法,由于会动会叫就是动物,所以大黄有两种类型,一种是动物,一种是狗
sapo(w1)
}
// 大黄在移动
// 大黄在吼叫
// 灰太狼在移动
// 灰太狼在吼叫
接口变量
接口是一种类型,所以一个变量可以定义为该类型。
如下所示,声明了一个动物类型的接口变量,当一个结构体实例对象拥有了move
与roar
方法后,才可成功进行赋值。
package main
import (
"fmt"
)
type animal interface {
move()
roar()
}
type dog struct {
name string
}
func (d dog) move() {
fmt.Printf("%s在移动\n", d.name)
}
func (d dog) roar() {
fmt.Printf("%s在吼叫\n", d.name)
}
func main() {
var a1 animal // 动物类型
d1 := dog{
name: "大黄",
}
a1 = d1 // 由于大黄有move与roar方法,所以它也算是动物类型。因此可以赋值成功
a1.move()
}
结构体方法类型
结构体不同的方法类型,会对接口产生不同的影响。
值接收者方法
当结构体的方法是值接收者方法时,该结构体实例化可以赋值给对应的接口变量,并且是任意形式。
package main
import (
"fmt"
)
type people interface {
combHair() // 能梳头就是人
}
type person struct {
name string
}
// 值接收者方法
func (p person) combHair() {
fmt.Printf("%s在梳头发", p.name)
}
func main() {
var pe people // 人类
var p1 = person{
name: "云崖",
}
// 全部都可以做为人类
pe = p1
pe = &p1
pe = *(&p1)
fmt.Println(pe)
}
指针接收者方法
当结构体的方法是指针接收者方法时,该结构体实例化赋值给接口变量时只能是地址传递。
package main
import (
"fmt"
)
type people interface {
combHair() // 能梳头就是人
}
type person struct {
name string
}
// 指针接收者方法
func (p *person) combHair() {
fmt.Printf("%s在梳头发", p.name)
}
func main() {
var pe people // 人类
var p1 = person{
name: "云崖",
}
pe = *p1 // 错误
pe = &p1 // 只能传递地址
pe = *(&p1) // 错误
fmt.Println(pe)
}
类型与接口
一个类型多个接口
如会梳头发是人类,会移动是动物类。
那么我们就可以对person
这个结构体做两个接口,一个是人类的接口,一个是动物类的接口。
person
实例化对象p1
同时满足人类和动物这两个接口。
package main
import (
"fmt"
)
type people interface {
combHair() // 能梳头就是人
}
type animal interface {
move() // 能移动就是动物
}
type person struct {
name string
}
func (p person) move() {
fmt.Println("人移动了")
}
func (p person) combHair(){
fmt.Println("人在梳头发")
}
func main() {
var pe people // 人类接口变量
var an animal // 动物接口变量
var p1 = person{ // 实例化出一个人
name: "云崖",
}
pe = p1
an = p1
fmt.Println(pe)
fmt.Println(an)
}
一个接口多个类型
如会动的都是动物,那么下面例子中狗和牛等结构体的实例化就都是动物。
这就是一个动物接口可以有多个类型的体现。
package main
import (
"fmt"
)
type people interface {
combHair() // 能梳头就是人
}
type animal interface {
move() // 能移动就是动物
}
type dog struct {
name string
}
type cattle struct {
name string
}
func (d dog) move() {
fmt.Println("狗在动")
}
func (c cattle) move() {
fmt.Println("牛在动")
}
func main() {
var an animal // 动物接口变量
d1 := dog{
name: "大黄",
}
c1 := cattle{
name: "牛魔王",
}
an = d1
fmt.Println(an)
an = c1
fmt.Println(an)
}
接口嵌套
比如两栖动物可以在水中生活,也可以在陆地生活。
我们定义一个陆地生活动物的接口,再定义一个水中生活动物的接口。
如何表示两栖动物的接口呢?只需要应用嵌套让两栖动物的接口同时拥有陆地生活动物接口的方法和水中生活动物的方法即可。
如下示例:
package main
import (
"fmt"
)
type terrestrialAnimal interface {
landMobile() // 陆地移动,则是陆生动物
}
type aquatic interface {
swim() // 能够游泳,则是水生动物
}
type amphibian interface {
terrestrialAnimal
aquatic
// 同时实现了陆生动物和水生动物的特性,则是两栖动物
}
type frog struct{
name string
// 青蛙结构体
}
// 青蛙会游泳
func (f frog) swim(){
fmt.Println("青蛙在游泳")
}
// 青蛙能上岸
func (f frog) landMobile(){
fmt.Println("青蛙在陆地上欢快的蹦跶")
}
func main() {
var amp amphibian // 两栖动物接口变量
var f1 = frog {
name : "呱呱娃",
}
amp = f1 // 青蛙是两栖动物
fmt.Println(amp)
}
空接口
空接口在实际开发中使用非常广泛,它代表可以是任意类型的变量。
接口定义
使用interface{}
来定义一个空接口。
如下所示:
package main
import (
"fmt"
)
func main(){
var arbitraryType interface{} // 接收任意类型的变量
var str = "HELLO,WORLD"
arbitraryType = str
fmt.Printf("%T \n", arbitraryType) // string
var num = int64(100)
arbitraryType = num
fmt.Printf("%T \n",arbitraryType) // int64
}
实际应用
空接口一般应用于函数形参,以及map
值中。
如下,该函数允许传递任何数据类型的数据。
package main
import (
"fmt"
)
func test(v1 interface{}) {
fmt.Printf("%T \n", v1) // int
}
func main() {
test(100)
}
其实更多的应用场景在map
中,如下定义的map
值可以是任意类型,这样存取都会变得很方便。
package main
import (
"fmt"
)
var m = make(map[string]interface{}, 30)
func main() {
m["k1"] = "第一个"
m["k2"] = 2
m["k3"] = []int16{1,2,3,4}
fmt.Print(m)
}
类型断言
由于interface{}
空接口可以存储任意类型的值,那么我们如何判断出值的类型?
方法如下:
x.(T)
x:表示类型为interface{}的变量
T:表示断言x可能是的类型。
方法断言的作用其实就类似于猜,这种用的不是很多。
如下示例,使用switch
进行类型断言的判断:
package main
import (
"fmt"
)
var m = make(map[string]interface{}, 30)
func test(v1 map[string]interface{}) {
for _, value := range v1 {
switch value.(type) { // 使用断言获取到类型
case string:
fmt.Println("这是一个字符串类型", value)
case int:
fmt.Println("这是一个int类型", value)
case []int16:
fmt.Println("这是一个int16的切片类型", value)
default:
fmt.Println("未识别的类型", value)
}
}
}
func main() {
m["k1"] = "第一个"
m["k2"] = 2
m["k3"] = []int16{1, 2, 3, 4}
test(m)
}
请发表评论