接口-2
紧接着接口-1开始学习接口新的知识!
接口查询
看到这个名词,我其实有点懵,接口查询是什么意思,字面理解是查询该类是否实现了哪个接口?就回归到我上文提到的,因为Go语言中没有明确的继承关系“非浸入式”所以不知道该类是实现了哪些接口,所以引入了接口查询吗?
在Go语言中对象是否满足某个接口,通过某个接口查询其他接口,这一切可以自动完成。在Windows有个COM对象,这个COM对象有个查询接口(QueryInterface),在Go语言中的接口查询和COM接口查询非常类似,可以通过某个接口来查询对象实现的其他接口(这得一个接口一个接口查询试错?),在COM中实现接口查询过程貌似非常繁复。COM对接口查询的介绍如下,在Go语言中同样适用。
>你会飞吗? // IFly
>不会。
>你会游泳吗? // ISwim
>会。
>你会吃饭吗? // IShout
>会。
> ...
理解:我要这样一直查询(问下去嘛)直到问完所有的?......天啦已经超出了人类的范畴!!!! 那么在Go语言中能一定知道接口它指向的对象是否是某个类型吗?比如:
var file1 Writer = ...
if file6, ok := file1.(*File); ok {
...
}
这个if语句判断file1接口指向的对象实例是否是*File类型,如果是则执行特定代码。
查询接口所指向的对象是否为某个类型的这种用法可以认为只是接口查询的一个特例。接口是对一组类型的公共特性的抽象,所以查询接口与查询具体类型的区别好比是下面这两句问话的区别:
>你是猴子吗?
>是。
>你是孙悟空吗?
>是。
第一句问话查的是一个群体,是查询接口;而第二句问话已经到了具体的个体,是查询具体类型。再难理解的话!这相当于你是人吗?是查询的接口。你是雷小鸿吗?是具体类型查询。
在java中也有类似的查询能力,比如查询一个对象是否继承自某个类型(基类查询),或者是否实现了某个接口(接口派生查询),但是java的动态查询与Go语言的动态查询不一样。比如:
> 你是逗比吗?
对于上面这个问题,基类查询看起来像是在这么问:“你老爸是逗比吗?”接口派生查询则看起来像是这么问:“你有逗比的执照没?”在Go语言中,则是先确定满足什么样的条件才是逗比,比如逗比技能要求有哪些(会讲笑话,走路搞怪)然后才是按条件一一拷问,只要满足了条件你就是逗比,而不关心你是否有逗比执照。那么接口派生怎么确定我是程序员呀?需要整个程序员执照......
类型查询
在Go语言中,可以更加直截了当地询问接口指向的对象实例的类型。如下:
var v1 interface{} = ...
switch v := v1.(type) {
case int: // 现在v的类型是int
case string: // 现在v的类型是string
...
}
就像现实生活中物种多得数不清一样,语言中的类型也多得数不清,所以类型查询并不经常使用。它更多是个补充,需要配合接口查询使用。如下:
type Stringer interface {
String() string
}
func Println(args ...interface{}) {
for _, arg := range args {
switch v := v1.(type) {
case int: // 现在v的类型是int
case string: // 现在v的类型是string
default:
if v, ok := arg.(Stringer); ok { // 现在v的类型是Stringer
val := v.String()
// ...
} else {
// ...
}
}
}
}
接口组合
很简单的理解就是把一个接口里面的方法组合成一个新的接口比如io中有读和写两个接口。
读接口
type Read interface {
Read(p []byte) (n int, err error)
}
写接口
type Writer interface {
Write(p []byte) (n int, err error)
}
组合成新的接口如下:
type ReadWriter interface {
Reader
Writer
}
等同于如下写法:
type ReadWriter interface {
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}
可以认为接口组合是类型匿名组合的一个特定场景,只不过接口只包含方法,而不包含任何成员变量。这个有点像我们开发中经常需要把很多方法重构组合在一起,给别人只暴露一个方法使用,不用别人挨个方法调用去使用。
Any类型
由于Go语言中任何对象实例都满足空接口interface{},所以interface{}看起来像是可以指向任何对象的Any类型,如下:
var v1 interface{} = 1 // 将int类型赋值给interface{}
var v2 interface{} = "abc" // 将string类型赋值给interface{}
var v3 interface{} = &v2 // 将*interface{}类型赋值给interface{}
var v4 interface{} = struct{ X int }{1}
var v5 interface{} = &struct{ X int }{1}
当函数可以接受任意的对象实例时,我们会将其声明为interface{},最典型的例子是标准库fmt中PrintXXX系列的函数,总体来说,interface{}类似于COM中的IUnknown,我们刚开始对其一无所知,但可以通过接口查询和类型查询逐步了解它。
总结
- 接口查询:你是猴子吗?
- 类型查询:你是猴子请来的逗比吗?
- 接口组合:把“你是猴子请来的逗比吗?”和“你是孙悟空吗?”组合成一个新的接口“你这个猴子是个逗比的孙悟空吗?"
备注
本文正在参与「掘金Golang主题学习月」, 点击查看活动详情。