Golang 之 接口

正式入坑go,感觉接口这里遇到了点阻碍,记录一下。

主要问题包括:接口定义,接口与多态,空接口,类型断言

一、概念

在Go中,接口是一组方法签名。当一个类型为接口中的所有方法提供定义时,它被称为实现该接口(即go语言的接口是 非侵入式 的,只要实现了接口中的所有方法,就实现了这个接口,不需要额外声明)。接口指定类型应具有的方法,类型决定如何实现这些方法。

简单来说,接口是一组仅包含 方法名,参数,返回值 的未具体实现的方法集合,就是只定义了一个对象的行为规范(方法),只定义规范不实现,由具体的对象来实现规范的细节。

接口就是一种特殊的类型,并且类似结构体,亦可以继承(嵌套)

二、接口代码实现

//定义接口
type interfaceName interface { 
    // 方法列表
    mothod_name1( parameter1...) (return_type1...)
    mothod_name2( parameter1...) (return_type1...)
    ...
} 
  • 当我们看到一个接口类型的值时,我们不了解它具体有什么内容,只知道通过它的方法能做什么

  • 当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问

  • 方法列表中参数名和变量名可以省略

  • 命名接口时,一般会在单词后面添加er,如有写操作的接口叫writer,有字符串功能的接口叫stringer等

接口与多态

多态指代码可以根据类型的具体实现采取不同行为的能力。如果一个类型实现了某个接口,所有使用这个接口的地方,都可以支持这种类型的值,下面例子即有两个类型实现了notifier接口。

在下面代码中,因为notifier接口里只有一个notify方法,所以我们只需要给useradmin分别实现notify方法就可以实现notifier接口了。

// 使用接口展示多态行为
package main

import "fmt"

// 定义接口 接口的命名一般采用er结尾
type notifier interface {
	notify()
}

// 用户信息
type user struct {
	name	string
	email	string
}

// notify()方法使用指针接收者实现了notifier接口,即方法的实现1
// 打印用户姓名和邮件信息
func (u *user) notify() {
	fmt.Printf("Sending user email to %s <%s> \n", u.name, u.email)
}

// 管理者信息
type admin struct {
	name	string
	email	string
}

// notify()方法使用指针接收者实现了notifier接口,即方法的实现2
// 打印管理者姓名和邮件信息
func (a *admin) notify() {
	fmt.Printf("Sending admin email to %s <%s> \n", a.name, a.email)
}

func main() {
	// 创建结构体user的值传入sendNotification函数,
	
        amy := user{"艾米", "[email protected]"}
	xiashe := admin{"侠奢", "[email protected]"}
      
        // 体现多态:接受不同变量
        sendNotification(&amy)
	sendNotification(&xiashe)
}

// 函数接收 接口类型, 接口类型变量能够存储所有实现了该接口的实例,即多态函数
// 函数接受了一个实现notifier接口的值
func sendNotification(n notifier)  {
	n.notify()
}

接口体现多态的第二种形式,接口类型变量 能够存储所有实现了该接口的实例, 例如下面的代码中,notifier类型的变量能够存储useradmin类型的变量。

	var a [2]notifier
	amy = user{"艾米", "[email protected]"}
	xiashe = admin{"侠奢", "[email protected]"}

	a[0] = &amy
	a[1] = &xiashe		

此外,一个类型也可以实现多个接口

三、空接口与类型断言

空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。

空接口类型的变量可以存储任意类型的变量

func main() {
	// 定义一个空接口x
	var x interface{}
        
        //空接口存储string变量
	a := "你好哇"
	x = a
	fmt.Printf("type:%T value:%v\n", x, x)

        //存储int类型
	b := 10086
	x = b
	fmt.Printf("type:%T value:%v\n", x, x)

        //存储bool类型
	c := true
	x = c
	fmt.Printf("type:%T value:%v\n", x, x)
}
空接口的应用
  • 使用空接口实现可以接收任意类型的函数参数。
// 空接口作为传入的函数参数
// 该实例打印出传入类型和参数
func show(a interface{}) {
	fmt.Printf("type:%T value:%v\n", a, a)
}
  • 使用空接口实现可以保存任意值的映射(map)
// 空接口作为map值
// 传入map值不确定时可以使用,但是map键不可以
	var studentInfo = make(map[string]interface{})
	studentInfo["name"] = "侠奢"
	studentInfo["age"] = 18
	studentInfo["married"] = false
	fmt.Println(studentInfo)
类型断言

既然空接口可以存储任意类型,那么如何区分不同的类型?
这里涉及到动态类型和动态值的问题...就此略过

下面写一个具体例子理解一下类型断言的操作

type writer interface{}

...

    var x Writer
    x = os.Stdout 
    x = new(bytes.Buffer)
    x = nil
    x = true

由上面代码可知,空接口可以传入多种不同类型,怎么判断上面x的类型呢?

我们可以用以下格式判断,x代表接口类型的变量,T代表可能的类型

x.(T)

下面写具体判别方法,主要有两种判别方法

  • 第一种,if-else
func main() {
	var x interface{}
	x = "你好啊"
	v, ok := x.(string)
	if ok {
		fmt.Println(v)
	} else {
		fmt.Println("类型断言失败")
	}
}
  • 第二种,switch方法
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!")
	}
}

最后搬运一个比较完整的断言例子

package main

import (
	"fmt"
)

// 定义一个结构体
type Student struct {
	Name string
}

// 类型断言
func main() {
	Params := make([]interface{}, 3)	//定义接口类型,较为常用
	Params[0] = 88                   // 整型
	Params[1] = "社会青年"         // 字符串
	Params[2] = Student{Name: "cbs"}   // 自定义结构体类型

	// Comma-ok断言
	for index, v := range Params {
		if _, ok := v.(int); ok {
			fmt.Printf("Params[%d] 是int类型 \n", index)
		} else if _, ok := v.(string); ok {
			fmt.Printf("Params[%d] 是字符串类型\n", index)
		} else if _, ok := v.(Student); ok {
			fmt.Printf("Params[%d] 是自定义结构体类型\n", index)
		} else {
			fmt.Printf("list[%d] 未知类型\n", index)
		}
	}

	// switch判断,这种比较OK
	for index, v := range Params {
		switch  value := v.(type) {
		case int:
			fmt.Printf("Params[%d] 是int类型, 值:%d \n", index,value)
		case string:
			fmt.Printf("Params[%d] 是字符串类型, 值:%s\n", index,value)
		case Student:
			fmt.Printf("Params[%d] 是Person类型, 值:%s\n", index,value)
		default:
			fmt.Printf("list[%d] 未知类型\n", index)
		}

	}
}

你可能感兴趣的:(Golang 之 接口)