Go 中的接口

很多面向对象的语言都有接口这个概念。Go 语言的接口的独特之处在于它是隐式实现。换句话说,对于一个具体的类型,无序声明它实现了哪些接口,只要提供接口所必需的方法即可。

接口类型

一个接口类型定义了一套方法,如果一个具体类型要实现该接口,那么必须实现接口类型定义的所有方法。

实现接口

如果一个类型实现了一个接口所要求的所有方法,那么这个类型就实现了这个接口。

	var w io.Writer
	w = os.Stdout  // OK: *os.File 有 Write 方法
	w = new(bytes.Buffer)  // OK: *bytes.Buffer 有 Write 方法
	w = time.Second    // 编译错误: time.Duration 缺少 write 方法
	
	var rwc io.ReadWriteCloser
	rwc = os.Stdout   // OK: *os.File 有 Read、Write、Close 方法
	rwc = new(bytes.Buffer) // 编译错误: *bytes.Buffer 缺少 Close 方法
	// 当右侧表达式也是一个接口时,该规则也有效:
	w = rwc
	rwc = w  // 编译错误:io.Writer 缺少 Close 方法

接口值

从概念上来讲,一个接口类型的值(简称接口值)其实有两个部分:一个具体类型和该类型的一个值。二者称为接口的动态类型和动态值。
接口值可以用 == 和 != 操作符来做比较。如果两个接口值都是 nil 或者二者的动态类型完全一致且二者动态值相等(使用动态类型的 == 操作符来做比较),那么两个接口值相等。因为接口值是可以比较的,所以它们可以作为 map 的键,也可以作为 switch 语句的操作数。
需要注意的是,在比较两个接口值时,如果两个接口值的动态类型一致,但对应的动态值是不可比较的(比如 slice),那么这个比较会以崩溃的方式失败:

var x interface{} = []int{1,2,3}
fmt.Println(x == x)  // 宕机:试图比较不可比较的类型  []int

类型断言

类型断言是一个作用在接口值上的操作,写出来类似于 x.(T),其中 x 是一个接口类型的表达式,而 T 是一个类型(称为断言类型)。类型断言会检查作为操作数的动态类型是否满足指定的断言类型。
这儿有两个可能。首先,如果断言类型T是一个具体类型,那么类型断言会检查 x 的动态类型是否就是 T。如果检查成功,类型断言的结果就是 x 的动态值,类型当然就是T。换句话说,类型断言就是用来从它的操作数中把具体的类型提取出来的操作。

	var w io.Writer
	w = os.Stdout
	f := w.(*os.File)  // 成功: f == os.Stdout
	c := w.(*bytes.Buffer)  // 崩溃:接口持有的是 *os.File, 不是 *bytes.Buffer

其次,如果断言类型T 是一个接口类型,那么类型断言检查 x 的动态类型是否满足 T。如果检查成功,动态值并没有提取出来,结果仍然是一个接口值。

使用类型断言来识别错误

考虑一下os 包中的文件操作返回的错误集合, I/O 会因为很多原因失败,但有三类原因通常必须单独处理:文件已存储(创建操作),文件没找到(读取操作)以及权限不足。os 包提供了三个帮助函数用来对错误进行分类:

package os

func IsExist(err error) bool
func IsNotExist(err error) bool
func IsPermission(err error) bool

func IsNotExist(err error) bool {
    //	注意:不健壮
    return strings.Contains(err.Error(), "file dose not exist")
}

但由于处理 I/O 错误的逻辑会随着平台的变化而变化,因此这种方式很不健壮,同样的错误可能会用完全不同的错误消息来报告。
一个更可靠的方法是用专门的类型来表示结构化的错误值。os 包定义了一个 PathError 类型来表示在与一个文件路径相关的操作上发生错误(比如 Open 或者 Delete),一个类似的 LinkError 用来表述在于两个文件路径相关的操作上发生的错误(比如 Symlink 和 Rename).

var ErrNotExist = errors.New("file dose not exist")

// IsNotExist 返回一个布尔值,该值表明错误是否代表文件或目录不存在

func IsNotExist(err error) bool {
	if pe, ok := err.(*os.PathError); ok {
		err = pe.Err
	}
	return err == syscall.ENOENT || err == ErrNotExist
}

类型分支

类型分支的最简单形式与普通分支语句类似,两个的差别是操作数改为 x.(type)

var x interface{}
	switch x.(type){
	case nil:
		
	case int, uint:
		
	case bool:
		
	case string:
		
	default:
		
	}

与普通的 switch 语句类似,分支是按顺序来判定的,当一个分支符合时,对应的代码会执行。分支的顺序在一个或多个接口类型时变得很重要,因为有可能两个分支都能满足。default 分支的位置无关紧要。另外,类型分支不允许使用 fallthrough。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值