运维八一 运维八一
首页
运维杂记
编程浅尝
周积跬步
专栏
生活
关于
收藏
  • 分类
  • 标签
  • 归档
Source (opens new window)

运维八一

运维,运维!
首页
运维杂记
编程浅尝
周积跬步
专栏
生活
关于
收藏
  • 分类
  • 标签
  • 归档
Source (opens new window)
  • Go

    • 前言

    • Go基础知识

    • Go基本语法

    • 实战项目:简单web服务

    • 基本数据类型

    • 内置运算符

    • 分支和循环

    • 函数 function

    • 结构体 struct

    • 方法 method

    • 实战项目:跟踪函数调用链

    • 接口 interface

      • 接口类型
        • 1. 接口类型
          • 1.1 典型的接口类型
          • 1.2 首字母小写的接口类型
          • 1.3 空接口类型
          • 1.4 接口类型变量
          • 1.5 类型断言
      • 接口定义
      • 接口运行时的表示
      • 接口的应用
    • 并发 concurrency

    • 指针

    • 实战项目:实现轻量级线程池

    • 实战项目:实现TCP服务器

    • go常用包

    • Gin框架

    • go随记

  • Python

  • Shell

  • Java

  • Vue

  • 前端

  • 编程浅尝
  • Go
  • 接口 interface
lyndon
2022-06-07
目录

接口类型

Go 应用骨架(Application Skeleton):指将应用代码中的业务逻辑、算法实现逻辑、错误处理逻辑等“皮肉”逐一揭去后所呈现出的应用结构。

这就好比下面这个可爱的 Gopher(地鼠)通过 X 光机所看到的骨骼结构:

img

从静态角度去看,能清晰地看到应用程序的组成部分以及各个部分之间的连接,将其理解为 Go 应用内部的耦合设计;

从动态角度去看,能看到这幅骨架上可独立运动的几大机构,可以理解为应用的并发设计。

一个良好的骨架设计决定了应用的健壮性、灵活性与扩展性,甚至是应用的运行效率。

# 1. 接口类型

接口类型是==由 type 和 interface 关键字定义的一组方法集合==,其中,方法集合唯一确定了这个接口类型所表示的接口。

# 1.1 典型的接口类型

如下 MyInterface 的定义:

type MyInterface interface {
    M1(int) error
    M2(io.Writer, ...string)
}
1
2
3
4

接口类型 MyInterface 所表示的接口的方法集合,包含两个方法 M1 和 M2。之所以称 M1 和 M2 为“方法”,更多是从这个接口的实现者的角度考虑的。但从上面接口类型声明中各个“方法”的形式上来看,这更像是不带有 func 关键字的函数名 + 函数签名(参数列表 + 返回值列表)的组合。

方法的参数列表中形参名字与返回值列表中的具名返回值,都不作为区分两个方法的凭据。

比如下面的 MyInterface 接口类型的定义与上面的 MyInterface 接口类型定义都是等价的:

type MyInterface interface {
    M1(a int) error
    M2(w io.Writer, strs ...string)
}

type MyInterface interface {
    M1(n int) error
    M2(w io.Writer, args ...string)
}
1
2
3
4
5
6
7
8
9

Go 语言要求接口类型声明中的方法必须是具名的,并且方法名字在这个接口类型的方法集合中是唯一的。Go 1.14 版本以后,Go 接口类型允许嵌入的不同接口类型的方法集合存在交集,但前提是交集中的方法不仅名字要一样,它的函数签名部分也要保持一致,也就是参数列表与返回值列表也要相同,否则 Go 编译器照样会报错。

type Interface1 interface {
    M1()
}
type Interface2 interface {
    M1(string) 
    M2()
}

type Interface3 interface{
    Interface1
    Interface2 // 编译器报错:duplicate method M1
    M3()
}
1
2
3
4
5
6
7
8
9
10
11
12
13

Interface3 嵌入了 Interface1 和 Interface2,但后两者交集中的 M1 方法的函数签名不同,导致了编译出错:

# 1.2 首字母小写的接口类型

在 Go 接口类型的方法集合中放入首字母小写的非导出方法也是合法的。

在 Go 标准库中也有非导出方法的接口类型定义,比如 context 包中的 canceler 接口类型,它的代码如下:

// $GOROOT/src/context.go

// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
    cancel(removeFromParent bool, err error)
    Done() <-chan struct{}
}
1
2
3
4
5
6
7
8

# 1.3 空接口类型

**空接口类型:**如果一个接口类型定义中没有一个方法,那么它的方法集合就为空。

比如下面的 EmptyInterface 接口类型:

type EmptyInterface interface {

}
1
2
3

这个方法集合为空的接口类型就被称为空接口类型,但通常不需要自己显式定义这类空接口类型,直接使用interface{}这个类型字面值作为所有空接口类型的代表就可以了。

# 1.4 接口类型变量

接口类型一旦被定义后,它就和其他 Go 类型一样可以用于声明变量,比如:

var err error   // err是一个error接口类型的实例变量
var r io.Reader // r是一个io.Reader接口类型的实例变量
1
2

这些类型为接口类型的变量被称为接口类型变量,如果没有被显式赋予初值,接口类型变量的默认值为 nil。如果要为接口类型变量显式赋予初值,就要为接口类型变量选择合法的右值。

Go 规定:如果一个类型 T 的方法集合是某接口类型 I 的方法集合的等价集合或超集,我们就说类型 T 实现了接口类型 I,那么类型 T 的变量就可以作为合法的右值赋值给接口类型 I 的变量。

如果一个变量的类型是空接口类型,由于空接口类型的方法集合为空,这就意味着任何类型都实现了空接口的方法集合,所以可以将任何类型的值作为右值,赋值给空接口类型的变量。

比如下面例子:

var i interface{} = 15 // ok
i = "hello, golang" // ok
type T struct{}
var t T
i = t  // ok
i = &t // ok
1
2
3
4
5
6

空接口类型的这一可接受任意类型变量值作为右值的特性,让他成为 Go 加入泛型语法之前唯一一种具有“泛型”能力的语法元素,包括 Go 标准库在内的一些通用数据结构与算法的实现,都使用了空类型interface{}作为数据元素的类。

# 1.5 类型断言

Go 语言支持接口类型变量赋值的“逆操作”,也就是通过接口类型变量“还原”它的右值的类型与值信息,这个过程被称为**“类型断言(Type Assertion)”**。

类型断言通常使用下面的语法形式:

v, ok := i.(T) 
1

其中 i 是某一个接口类型变量,如果 T 是一个非接口类型且 T 是想要还原的类型,那么这句代码的含义就是断言存储在接口类型变量 i 中的值的类型为 T。

如果接口类型变量 i 之前被赋予的值确为 T 类型的值,那么这个语句执行后,左侧“comma, ok”语句中的变量 ok 的值将为 true,变量 v 的类型为 T,它值会是之前变量 i 的右值。如果 i 之前被赋予的值不是 T 类型的值,那么这个语句执行后,变量 ok 的值为 false,变量 v 的类型还是那个要还原的类型,但它的值是类型 T 的零值。

类型断言也支持下面这种语法形式:

v := i.(T)
1

但在这种形式下,一旦接口变量 i 之前被赋予的值不是 T 类型的值,那么这个语句将抛出 panic。如果变量 i 被赋予的值是 T 类型的值,那么变量 v 的类型为 T,它的值就会是之前变量 i 的右值。由于可能出现 panic,所以并不推荐使用这种类型断言的语法形式。

类型断言的语义:

var a int64 = 13
var i interface{} = a
v1, ok := i.(int64) 
fmt.Printf("v1=%d, the type of v1 is %T, ok=%t\n", v1, v1, ok) // v1=13, the type of v1 is int64, ok=true
v2, ok := i.(string)
fmt.Printf("v2=%s, the type of v2 is %T, ok=%t\n", v2, v2, ok) // v2=, the type of v2 is string, ok=false
v3 := i.(int64) 
fmt.Printf("v3=%d, the type of v3 is %T\n", v3, v3) // v3=13, the type of v3 is int64
v4 := i.([]int) // panic: interface conversion: interface {} is int64, not []int
fmt.Printf("the type of v4 is %T\n", v4) 
1
2
3
4
5
6
7
8
9
10

如果v, ok := i.(T)中的 T 是一个接口类型,那么类型断言的语义就会变成:断言 i 的值实现了接口类型 T。

如果断言成功,变量 v 的类型为 i 的值的类型,而并非接口类型 T。

如果断言失败,v 的类型信息为接口类型 T,它的值为 nil

T 为接口类型的示例:

type MyInterface interface {
    M1()
}

type T int
               
func (T) M1() {
    println("T's M1")
}              
               
func main() {  
    var t T    
    var i interface{} = t
    v1, ok := i.(MyInterface)
    if !ok {   
        panic("the value of i is not MyInterface")
    }          
    v1.M1()    
    fmt.Printf("the type of v1 is %T\n", v1) // the type of v1 is main.T
               
    i = int64(13)
    v2, ok := i.(MyInterface)
    fmt.Printf("the type of v2 is %T\n", v2) // the type of v2 is <nil>
    // v2 = 13 //  cannot use 1 (type int) as type MyInterface in assignment: int does not implement MyInterface (missing M1   method) 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

通过the type of v2 is ,其实是看不出断言失败后的变量 v2 的类型的,但通过最后一行代码的编译器错误提示,我们能清晰地看到 v2 的类型信息为 MyInterface。

上次更新: 2022/06/12, 15:48:09
总结
接口定义

← 总结 接口定义→

最近更新
01
ctr和crictl显示镜像不一致
03-13
02
alpine镜像集成常用数据库客户端
03-13
03
create-cluster
02-26
更多文章>
Theme by Vdoing | Copyright © 2015-2024 op81.com
苏ICP备18041258号-2
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式