Go语言接口:深入理解interface

发表时间: 2023-10-23 11:09

概述:

在Go语言中, 接口(interface)是一个自定义类型, 接口类型具体描述了一系列方法的集合。

接口类型是一种抽象的类型, 它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合,

golang接口

它们只会展示出它们自己的方法。 因此接口类型不能将其实例化。

Go通过接口实现了鸭子类型(duck-typing): "当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也想鸭子, 那么这只鸟就可以被称为鸭子"。我们并不关心对象是什么类型, 到底是不是鸭子, 只关心行为。

接口的使用

接口定义

type Humaner interface {    SayHi()}

接口命名习惯以er结尾

接口只有方法声明, 没有实现, 没有数据字段

接口可以匿名嵌入其他接口, 或嵌入到结构中

接口实现

接口是用来定义行为的类型。 这些被定义的行为不由接口直接实现, 而是通过方法由用户定义的类型实现, 一个实现了这些方法的具体类型是这个接口的实例。

如果用户定义的类型实现了某个接口类型声明的一组方法, 那么这个用户定义的类型的值就可以赋给这个接口类型的值。这个赋值会把用户定义的类型的值存入接口类型的值。

【实例1】

package main //必须有个main包import (    "fmt")type Printer interface { //接口类型    Print()}type user struct {    name string    age byte}func (u user) Print() {		fmt.Printf("%+v\n", u) //%v 基本格式的值。当输出结构体时,扩展标志(%+v)添加成员的名字。}func main() {    var u user    u.name = "Tom"    u.age = 29    // 探讨 这里 u(数据类型 user) 为何能传递给 变量 p (数据类型 Printer) ?    // 原因在于: u(数据类型 user) 已经实现了 p (数据类型 Printer) 接口中定义的方法    var p Printer = u //把对象赋值给接口    p.Print() //{name:Tom age:29}    var q Printer = user{"mary", 20}    q.Print() //{name:mary age:20}}

【实例2】

package main //必须有个main包import "fmt"type Humaner interface {    //方法, 只有声明, 没有实现, 由别的类型(自定义类型)实现    SayHi()}type Student struct { //学生    name string    score float64}//Student实现SayHi()方法func (s *Student) SayHi() {    fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)}type Teacher struct {    name string    group string}//Teacher实现SayHi()方法func (t *Teacher) SayHi() {    fmt.Printf("Teacher[%s, %s] say hi!!\n", t.name, t.group)}func main() {    var s Humaner = &Student{"mike", 88.88} //这里必须使用"&" 引用语义    s.SayHi() //Student[mike, 88.880000] say hi!!    var t Humaner = &Teacher{"yoyo", "Go语言"} //这里必须使用"&" 引用语义    t.SayHi() //Teacher[yoyo, Go语言] say hi!!    stu := &Student{"xuchenkai", 100}    stu.SayHi() //Student[xuchenkai, 100.000000] say hi!!    (*stu).SayHi() //Student[xuchenkai, 100.000000] say hi!!}

【实例3】

package main //必须有个main包import "fmt"type Humaner interface {    //方法, 只有声明, 没有实现, 由别的类型(自定义类型)实现    SayHi()}type Student struct { //学生    name string    score float64}//Student实现SayHi()方法func (s *Student) SayHi() {    fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)}type Teacher struct {    name string    group string}//Teacher实现SayHi()方法func (t *Teacher) SayHi() {    fmt.Printf("Teacher[%s, %s] say hi!!\n", t.name, t.group)}type MyStr string    //MyStr实现SayHi()方法    func (str MyStr) SayHi(){    fmt.Printf("MyStr[%s] say hi!!\n", str)}//普通函数, 参数为Humaner类型的变量ifunc WhoSayHi(i Humaner){    i.SayHi()}func main(){    s := &Student{"mike", 88.88}    t := &Teacher{"yoyo", "Go语言"}    var tmp MyStr = "测试"    s.SayHi() //Student[mike, 88.880000] say hi!!    t.SayHi() //Teacher[yoyo, Go语言] say hi!!    tmp.SayHi() //MyStr[测试] say hi!!    //多态, 调用同一接口, 不同实现    WhoSayHi(s) //Student[mike, 88.880000] say hi!!    WhoSayHi(t) //Teacher[yoyo, Go语言] say hi!!    WhoSayHi(tmp) //MyStr[测试] say hi!!    x := make([]Humaner, 3)    //这三个都是不同的元素, 但是他们实现了interface同一个接口    x[0], x[1], x[2] = s, t, tmp    for _, value := range x {    value.SayHi()}/*Student[mike, 88.880000] say hi!!Teacher[yoyo, Go语言] say hi!!MyStr[测试] say hi!!*/}

通过上面的代码, 你会发现接口就是一组抽象方法的集合, 它必须由其他非接口类型实现, 而不能自我实现。

常见的错误: 定义的接口必须实现

package main //必须有个main包import (    "fmt")type Printer interface { //接口类型    Print() // 定义打印的方法    Composition() // 定义排版的方法}type user struct {    name string    age byte}func (u user) Print() {    fmt.Printf("%+v\n", u) //%v 基本格式的值。当输出结构体时,扩展标志(%+v)添加成员的名字。}func main() {    var u user    u.name = "Tom"    u.age = 29    // 探讨 这里 u (数据类型 user) 为何能传递给 变量 P (数据类型 Printer) ?    // 原因在于: u(数据类型 user) 已经实现了 p (数据类型 Printer) 接口中定义的方法    var p Printer = u //把对象赋值给接口    p.Print() //{name:Tom age:29}    var q Printer = user{"mary", 20}    q.Print() //{name:mary age:20}}/*接口中定义的方法 Composition() 没有实现, 所报的错误# command-line-arguments.\demo42.go:25:6: cannot use u (type user) as type Printer in assignment:user does not implement Printer (missing Composition method).\demo42.go:28:6: cannot use user{...} (type user) as type Printer in assignment:user does not implement Printer (missing Composition method)*/

接口组合

接口嵌入

如果一个interface1 作为 interface2 的一个嵌入字段, 那么interface2 隐式的包含了 interface1 里面的方法。

package mainimport(    "fmt")type Humaner interface {    SayHi()}type Personer interface {    Humaner //匿名字段, 继承了SayHi()方法    Sing(lyrics string)}type Student struct {    name string    score float64}//Student实现SayHi()方法func (s *Student) SayHi() {    fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)}//Student实现sing()方法func (s *Student) Sing(lyrics string) {    fmt.Printf("Student sing[%s]!!\n", lyrics)}func main(){    s := &Student{"mike", 88.88}    var i2 Personer    i2 = s    /*    // 第二种写法    s := Student{"mike", 88.88}    var i2 Personer    i2 = &s //这里必须使用 "&" 引用语义    //第三种写法    var i2 Personer = &Student{"mike", 88.88}    */    i2.SayHi() //Student[mike, 88.880000] say hi!!    i2.Sing("学生哥") //Student sing[学生哥]!!}

接口转换

超集接口对象可转换为子集接口(原因在于超集继承了子集的方法, 超级集成的方法多余子集), 反之出错:

package mainimport(    "fmt")type Humaner interface { //子集    SayHi()}type Personer interface { //超集    Humaner //匿名字段, 继承了SayHi()方法    Sing(lyrics string)}type Student struct {    name string    score float64}//Student实现SayHi()方法func (s *Student) SayHi() {    fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)}//Student实现sing()方法func (s *Student) Sing(lyrics string) {    fmt.Printf("Student sing[%s]!!\n", lyrics)}func main(){    //超级可以转化子集, 反过来不可以    //Personer 为超级 Humaner为子集    //var i1 Humaner = &Student{"mike", 88.88}    //var i2 Personer = i1 //err 子集(i1 Humaner)不能转化为超级(i2 Personer)    var i1 Personer = &Student{"mike", 88.88}    var i2 Humaner = i1 //超级(i1 Personer)能够转换为子集(i2 Humaner)    i2.SayHi() //Student[mike, 88.880000] say hi!!    i1.Sing("xuchenkai") //Student sing[xuchenkai]!!    /*    //可以直接使用    var i1 Humaner = &Student{"mike", 88.88}    i1.SayHi() //Student[mike, 88.880000] say hi!!    var i2 Personer = &Student{"yoyo", 86.88}    i2.SayHi() //Student[yoyo, 86.860000] say hi!!    */}

空接口

没有包含方法的接口称为空接口, 空接口表示为"interface{}"。由于空接口没有方法, 因此所有类型都实现了空接口, 因此空接口可以存储任意类型的数值。

var v1 interface{} = 1 //将int类型赋值给interface{}

var v2 interface{} = "abc" //将string类型赋值给interface{}

var v3 interface{} = &v2 //将*interface{}类型赋值给interface{}

var v4 interface{} = struct{ X int }定义

var v5 interface{} = &struct{ X int }定义

当函数可以接受任意的对象实例时, 我们会将其声明为interface{}, 最典型的例子是标准库fmt中PrintXXX系列函数, 例如:

func Printf(fmt string, args ...interface{})

func Println(args ...interface{})

package mainimport(    "fmt")func xxx(arg ...interface{}){}func main(){    var i interface{} = 1    fmt.Println("i = ", i)    i = "abc"    fmt.Println("i = ", i)}

输出结果:

i = 1

i = abc

Type assertion 类型声明(断言)

mock/mock.go

package mock//定义结构体type Retriever struct {    Contents string}//接口实现 在main.go中定义的func (r Retriever) Get(url string) string {    return r.Contents}

real/real.go

package realimport (    "net/http"    "net/http/httputil"    "time")//定义结构体type Retriever struct {    UserAgent string    TimeOut time.Duration}//接口实现 在main.go中定义的func (r *Retriever) Get(url string) string {    resp, err := http.Get(url)    if err != nil {    panic(err)    }    result, err := httputil.DumpResponse(resp, true)    resp.Body.Close()    if err != nil {    panic(err)    }    return string(result)}

main.go

package mainimport (    "fmt"    "time"    "xiangmu4/mock"    "xiangmu4/real")//定义接口type Retriever interface {    Get(url string) string}func download(r Retriever) string {    return r.Get("http://www.imooc.com")}func main() {    var r Retriever    r = mock.Retriever{"this is a fake imooc.com"} //给结构体赋值    //fmt.Printf("%T %v\n", r, r) //查看r类型 mock.Retriever {this is a fake imooc.com}    inspect(r)    r = &real.Retriever{    UserAgent: "Mozilla/5.0",    TimeOut: time.Minute,    } //给结构体赋值    //fmt.Printf("%T %v\n", r, r) //查看r类型 real.Retriever { 0s}    //fmt.Println(download(r))    inspect(r)    //Type assertion 类型声明    if mockRetriever, ok := r.(mock.Retriever); ok {    fmt.Println(mockRetriever.Contents)    } else {    fmt.Println("not a mock retriever")    }    //Type assertion 类型声明    if realRetriever, ok := r.(*real.Retriever); ok { //Type assertion 类型声明 r.(*real.Retriever)    fmt.Println(realRetriever.TimeOut)    } else {    fmt.Println("not a mock retriever")    }}func inspect(r Retriever) {    fmt.Printf("%T %v\n", r, r) //查看结构体信息    switch v := r.(type) {    case *mock.Retriever:    		fmt.Println("Contents:", v.Contents)    case *real.Retriever:    		fmt.Println("UserAgent:", v.UserAgent)    }}

注意: 项目名称xiangmu4, 以下文件均放在xiangmu4目录下, 采用go mod 依赖模式