Go语言基础之接口

接口

接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。

 

接口类型

在Go语言中接口是一种类型,一种抽象的类型。

接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)

 

接口的定义

Go语言提倡面向接口编程。每个接口由数个方法组成。

type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
    …
}
  • 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
  • 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。

 

实现接口的条件

一个对象只要全部实现了接口的方法,那么就实现了这个接口

type dog struct {
	name string
}

type cat struct{}

// 实现狗的 sayHi 方法
func (d dog) sayHi() {
	fmt.Println("汪汪汪~~~")
}

// 实现猫的 sayHi 方法
func (c cat) sayHi() {
	fmt.Println("喵喵喵~~~")
}

// sayer 接口
type sayer interface {
	sayHi()
}

 

接口类型变量

接口类型变量能够存储所有实现了该接口的实例。 例如上面的示例中,Sayer类型的变量能够存储dogcat类型的变量。

func main() {
	var c sayer              // 声明一个sayer类型的变量c
	a := dog{name: "旺财"}   //实例化一个dog
	c = a                    // 将dog实例直接赋值给c
	fmt.Printf("%#v\n", c)   // main.dog{name:"旺财"}
	c.sayHi()                // 汪汪汪~~~
	b := cat{}               //实例化一个cat
	c = b
	b.sayHi() 				// 喵喵喵~~~
}

 

Go中描述的方法集

values                                    Methods Receivers

--------------------------------------------------------------------

T                                                     (t  T)

*T                                                    (t  T)and (t  *T)

 

从接收者角度来看方法集

Methods Receivers                          values                                    

--------------------------------------------------------------------

(t  T)                                             T and *T        

(t  *T)                                            *T

 

值接收者实现接口

type dog struct {
	name string
}

// Mover 为动作接口
type Mover interface {
	move()
}

func (d dog) move() {
	fmt.Printf("%s在快速奔跑", d.name)
}

func main() {
	var x Mover
	d1 := dog{"旺财"}
	x = d1
	fmt.Println(x) // {旺财}
	d2 := &dog{"富贵"}
	x = d2
	fmt.Println(x) // &{富贵}
	x.move()       // 富贵在快速奔跑
}

无论是dog结构体还是指针结构体的*dog类型变量都可以赋值给该接口变量。

 

指针接收者实现接口

func (d *dog) move() {
	fmt.Printf("%s在快速奔跑", d.name)
}

func main() {
	var x Mover
	//d1 := dog{"旺财"}
	//x = d1		// 编译不通过
	d2 := &dog{"富贵"}
	x = d2
	fmt.Println(x) // &{富贵}
	x.move()       // 富贵在快速奔跑
}

此时接口实现的是*dog类型,直接将x传入是编译不通过的,此时只能是存储*dog的值。

 

一个类型实现多个接口

一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。

type dog struct {
	name string
}

// Mover 为动作接口
type Mover interface {
	move()
}

// Sayer 为说话接口
type Sayer interface {
	sayHi()
}

func (d *dog) move() {
	fmt.Printf("%s在快速奔跑\n", d.name)
}

func (d *dog) sayHi() {
	fmt.Printf("%s在叫唤\n", d.name)
}
func main() {
	var x Mover
	d2 := &dog{"富贵"}
	x = d2
	x.move() // 富贵在快速奔跑
	var y Sayer = &dog{"旺财"}
	y.sayHi()
}

 

多个类型实现同一个接口

type dog struct {
	name string
}

type car struct{
	name string
}
// Mover 为动作接口
type Mover interface {
	move()
}

func (c *car) move(){
	fmt.Printf("%s在快速奔跑\n", c.name)
}

func (d *dog) move() {
	fmt.Printf("%s在快速奔跑\n", d.name)
}

这个时候就不必关心是什么物体了,只要调用他们的move方法就可以了

func main() {
	var x Mover
	d1 := &dog{"富贵"}
	x = d1
	x.move() // 富贵在快速奔跑
	var y Mover = &car{"兰博"}
	y.move()
}

并且一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。

// WashingMachine 洗衣机
type WashingMachine interface {
	wash()
	dry()
}

// 甩干器
type dryer struct{}

// 实现WashingMachine接口的dry()方法
func (d dryer) dry() {
	fmt.Println("甩一甩")
}

// 海尔洗衣机
type haier struct {
	dryer //嵌入甩干器
}

// 实现WashingMachine接口的wash()方法
func (h haier) wash() {
	fmt.Println("洗刷刷")
}

 

接口嵌套

接口也可以通过接口嵌套实现新接口

// Sayer 接口
type Sayer interface {
	say()
}

// Mover 接口
type Mover interface {
	move()
}

// 接口嵌套
type animal interface {
	Sayer
	Mover
}

 

空接口

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

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

func main() {
	var x interface{}
	s := "字符串"
	x = s
	fmt.Printf("type:%T value:%v\n", x, x)
	i := 100
	x = i
	fmt.Printf("type:%T value:%v\n", x, x)
	b := true
	x = b
	fmt.Printf("type:%T value:%v\n", x, x)
}

 

空接口的应用

空接口作为函数的参数

// 空接口作为函数参数
func show(a interface{}) {
	fmt.Printf("type:%T value:%v\n", a, a)
}

空接口作为map的值

使用空接口实现可以保存任意值的字典

func main() {
	var students = make(map[string]interface{}, 10)
	students["name"] = "三藏"
	students["age"] = 99
	fmt.Println(students)
}

 

类型断言

想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:

x.(T)
x 表示类型为interface{}的变量
T 表示断言x可能的类型

返回两个值,第一个值为x参数转换为T后的值,第二个值为布尔,真为True,假为False

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

 

判断是否实现了接口

type Reader interface {
	Read()
}

type Writer interface {
	Write()
}

type ReadWriter interface {
	Reader
	Writer
}

type File struct {
}

func (f *File) Read() {
	fmt.Println("read data")
}

func (f *File) Write() {
	fmt.Println("write data")
}

func Test(rw ReadWriter) {
	rw.Read()
	rw.Write()
}

func main() {
	var f *File
	var b interface{}
	b = f
	//Test(&f)
	v, ok := b.(ReadWriter)
	fmt.Println(v, ok)
}

只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值