《Go语言圣经》学习笔记:7.Go接口详解

7. 接口

7.1 什么是接口

接口是一组契约(或者称为规定,合约,行为以及方法等等)。这就好比家里的插座接口,有些插座的插座的接口可以插入不同的插头。

7.2 接口定义与格式

接口(interface)是一种类型,用来定义行为(方法)。这句话有两个重点,类型定义行为

首先解释定义行为:
接口即一组方法定义的集合,定义了对象的一组行为,就是定义了一些函数,由具体的类型实例实现具体的方法。
换句话说,一个接口就是定义(规范或约束),接口并不会实现这些方法,具体的实现由类实现,实现接口的类必须严格按照接口的声明来实现接口提供的所有功能。

为什么要有接口?

接口的作用应该是将定义与实现分离,降低耦合度。
在多人合作开发同一个项目时,​接口表示调用者和设计者的一种约定,事先定义好相互调用的接口可以大大提高开发的效率。有了接口,就可以在不影响现有接口声明的情况下,修改接口的内部实现,从而使兼容性问题最小化。

接口的定义格式:

type Namer interface {
    Method1(param_list) return_type  //方法名(参数列表) 返回值列表
    Method2(param_list) return_type  //方法名(参数列表) 返回值列表
}

7.3 接口的实现和实现的条件

怎么实现接口:

实现接口的类(结构体或者函数,并不一定是结构体)并不需要显式声明,只需要实现接口所有的函数就表示实现了该接口,而且类还可以拥有自己的方法。

Go 语言的接口是隐式的,只要类上定义的方法在形式上(名称、参数和返回值)和接口定义的一样,那么这个类自动实现了这个接口,我们就可以使用这个接口变量来指向这个类对象。

接口能被哪些类型实现:
接口可以被结构体实现,也可以被函数类型实现,当然也可以使用自定义。
接口被实现的条件:
接口被实现的条件一:接口的方法与实现接口的类型方法格式一致(方法名、参数类型、返回值类型一致)。
接口被实现的条件二:接口中所有方法均被实现。

结构体以及自定义类型接口的实现:

package main

import "fmt"

type Calculator interface {
	Add(x int64)
	Sub(x int64)
}

type myInt struct {
	data int64
}

func (p *myInt) Add(x int64)  {
	p.data += x
}

func (p *myInt) Sub(x int64)  {
	p.data -= x
}

// 结构体也可以拥有自己的方法,这个方法并没有在接口中
func (p *myInt) Value() int64 {
	return p.data
}

type myInt2 int64

func (p *myInt2) Add(x int64)  {
	*p += myInt2(x)
}

func (p *myInt2) Sub(x int64)  {
	*p -= myInt2(x)
}


func main()  {
    var cal Calculator		// 接口
    
	p1 := myInt{100}
	cal = &p1				// 由于实现的是*myInt的方法,所以需要取址
	cal.Add(10)
	cal.Sub(2)
	// cal.Value() 			// 错误,接口没有实现
    fmt.Println(p1.Value())	// 108(100+10-2)
    
    // 试试不通过接口来操作
	p1.Add(10)
	p1.Sub(2)
    fmt.Println(p1.data)	// 116(108+10-2)
    
    // 自定义的类型,并非结构体
	p2 := myInt2(20)
	cal = &p2
	cal.Add(10)
	cal.Sub(2)
    fmt.Println(p2)			// 28(20+10-2)
}

函数体的接口实现:

package main

import (
    "fmt"
)

// 调用器接口
type Invoker interface {
    // 需要实现一个Call方法
    Call(interface{})
}

// 结构体类型
type Struct struct {
}

// 实现Invoker的Call
func (s *Struct) Call(p interface{}) {
    fmt.Println("from struct", p)
}

// 函数定义为类型
type FuncCaller func(interface{})

// 实现Invoker的Call
func (f FuncCaller) Call(p interface{}) {

    // 调用f函数本体
    f(p)
}

func main() {

    // 声明接口变量
    var invoker Invoker

    // 实例化结构体
    s := new(Struct)

    // 将实例化的结构体赋值到接口
    invoker = s

    // 使用接口调用实例化结构体的方法Struct.Call
    invoker.Call("hello")

    // 将匿名函数转为FuncCaller类型,再赋值给接口
    invoker = FuncCaller(func(v interface{}) {
        fmt.Println("from function", v)
    })

    // 使用接口调用FuncCaller.Call,内部会调用函数本体
    invoker.Call("hello")
}

总结:只要使用了type定义了一个新的类型,并且实现的接口的所有方法,那么这个类型就可以赋值给这个接口。还有,如果想要对类型的一些成员进行修改,那么应该使用指针作为接收器来实现其方法。

7.4 接口赋值

现在来解释接口是一个类型,本质是一个指针类型,那么什么样的值可以赋值给接口,有两种:实现了该接口的类或者接口

1.将对象赋值给接口
当接口实例中保存了自定义类型的实例后,就可以直接从接口上调用它所保存的实例的方法。

package main
 
import (
    "fmt"
)
 
//定义接口
type Testinterface interface{
    Teststring() string
    Testint() int
}
 
//定义结构体
type TestMethod struct{
    name string
    age int
}
 
//结构体的两个方法隐式实现接口
func (t *TestMethod)Teststring() string{
    return t.name
}
 
func (t *TestMethod)Testint() int{
    return t.age
}
 
func main(){
    T1 := &TestMethod{"ling",34}
    T2 := TestMethod{"gos",43}
    //接口本质是一种类型
    //接口赋值:只要类实现了该接口的所有方法,即可将该类赋值给这个接口
    var Test1 Testinterface  //接口只能是值类型
    Test1 = T1   //TestMethod类的指针类型实例传给接口
    fmt.Println(Test1.Teststring())
    fmt.Println(Test1.Testint())
 
    Test2 := T2   //TestMethod类的值类型实例传给接口
    fmt.Println(Test2.Teststring())
    fmt.Println(Test2.Testint())
}

2.将接口赋值给另一个接口

1.只要两个接口拥有相同的方法列表(与次序无关),即是两个相同的接口,可以相互赋值
2.接口赋值只需要接口A的方法列表是接口B的子集(即假设接口A中定义的所有方法,都在接口B中有定义),那么B接口的实例可以赋值给A的对象。反之不成立,即子接口B包含了父接口A,因此可以将子接口的实例赋值给父接口。
3.即子接口实例实现了子接口的所有方法,而父接口的方法列表是子接口的子集,则子接口实例自然实现了父接口的所有方法,因此可以将子接口实例赋值给父接口。

3.接口类型作为参数

第一点已经说了可以将实现接口的类赋值给接口,而将接口类型作为参数很常见。这时,那些实现接口的实例都能作为接口类型参数传递给函数/方法。

package main
import (
    "fmt"
)
//Shaper接口
type Shaper interface {
    Area() float64
}
// Circle struct结构体
type Circle struct {
    radius float64
}
// Circle类型实现Shaper中的方法Area()
func (c *Circle) Area() float64 {
    return 3.14 * c.radius * c.radius
}
 
func main() {
    // Circle的指针类型实例
    c1 := new(Circle)
    c1.radius = 2.5
    //将 Circle的指针类型实例c1传给函数myArea,接收类型为Shaper接口
    myArea(c1)
}
func myArea(n Shaper) {
    fmt.Println(n.Area())
}

7.5 空接口

空接口是指没有定义任何接口方法的接口。没有定义任何接口方法,意味着Go中的任意对象都已经实现空接口(因为没方法需要实现),只要实现接口的对象都可以被接口保存,所以任意对象都可以保存到空接口实例变量中

空接口的定义方式:

type empty_int interface{}

更常见的,会直接使用interface{}作为一种类型,表示空接口。例如:

var i interface{}

再比如函数使用空接口类型参数:

func myfunc(i interface{})

如何使用空接口

可以定义一个空接口类型的array、slice、map、struct等,这样它们就可以用来存放任意类型的对象,因为任意类型都实现了空接口。

package main
 
import "fmt"
 
func main() {
    any := make([]interface{}, 5)
    any[0] = 11
    any[1] = "hello world"
    any[2] = []int{11, 22, 33, 44}
    for _, value := range any {
        fmt.Println(value)
    }
}

结果:

11
hello world
[11 22 33 44]
<nil>
<nil>

通过空接口类型,Go也能像其它动态语言一样,在数据结构中存储任意类型的数据。

7.6 接口嵌套

接口可以嵌套,嵌套的内部接口将属于外部接口,内部接口的方法也将属于外部接口。

另外在类型嵌套时,如果内部类型实现了接口,那么外部类型也会自动实现接口,因为内部属性是属于外部属性的。

type ReadWrite interface {
    Read(b Buffer) bool
    Write(b Buffer) bool
}
  
type Lock interface {
    Lock()
    Unlock()
}
  
type File interface {<br>  //ReadWrite为内部接口
    ReadWrite<br>  //Lock为内部接口
    Lock
    Close()
}

7.7 类型断言

类型断言为判断一个类型有没有实现接口。

假如我现在写了一个结构体类型 MyFile 来实现上面的 File 接口,那么我如何知道 MyFile 是否实现了 File 接口呢?

package main
  
import "fmt"
  
type MyFile struct{}
  
func (m *MyFile) Read() bool {
    fmt.Printf("Read()\n")
    return true
}
  
// ...
// 假设我这里相继实现了 Write(), Lock(),Unlock() 和 Close() 方法
  
func main() {
    my := new(MyFile)
    fIntf := File(my)
  
    // 看这里,看这里
    if v, ok := fIntf.(*MyFile); ok {
        v.Read()
    }
}  

类型断言的格式:

if v, ok : = varI.(T) ; ok { <br>   // checked type assertion<br>     //do something
    return
}

如果 vvarI 转换到类型 T 的值,ok 会是 true;否则 v 是类型 T 的零值,okfalse

要是多个类型实现了同一个接口,比如前面的 areaIntf,要如何测试呢?
那就要用 type-switch 来判断了。

switch t := areaIntf.(type) {
case *Rectangle:
    // do something
case *Triangle:
    // do something
default:
    // do something
}

7.8 多态

1、多个类型(结构体)可以实现同一个接口。
2、一个类型(结构体)可以实现多个接口。
3、实现接口的类(结构体)可以赋值给接口。

package main
 
import "fmt"
 
type Shaper interface {
    Area() float64
}
 
// ==== Rectangle ====
type Rectangle struct {
    length float64
    width  float64
}
 
// 实现 Shaper 接口中的方法
func (r *Rectangle) Area() float64 {
    return r.length * r.width
}
 
// Set 是属于 Rectangle 自己的方法
func (r *Rectangle) Set(l float64, w float64) {
    r.length = l
    r.width = w
}
 
 
// ==== Triangle ====
type Triangle struct {
    bottom float64
    hight  float64
}
 
func (t *Triangle) Area() float64 {
    return t.bottom * t.hight / 2
}
 
func (t *Triangle) Set(b float64, h float64) {
    t.bottom = b
    t.hight = h
}
 
// ==== Triangle End ====
 
func main() {
    rect := new(Rectangle)
    rect.Set(2, 3)
    areaIntf := Shaper(rect) //这种方法只能将指针类型的类示例赋值给接口
    fmt.Printf("The rect has area: %f\n", areaIntf.Area())
 
    triangle := new(Triangle)
    triangle.Set(2, 3)
    areaIntf = Shaper(triangle) //这种方法只能将指针类型的类示例赋值给接口
    fmt.Printf("The triangle has area: %f\n", areaIntf.Area())
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值