Golang 的 interface(接口) 是一个很神奇的东西,定义为:

  • 接口类型是由一组方法定义的集合
  • 接口类型的值可以存放实现这些方法的任何值

也就是说,只要一个值实现了这个接口的方法,那么他就是这个接口。最简单的例子是一个空的接口interface{}。因为这个接口是空的, 那么所有的类型,都可以认为是实现了这个接口,那么他就是这个接口。所以函数

func foo(a interface{}) {
  return
}

是可以接受任何类型的值作为参数的。 定义接口的语法如下:

type fooInterface interface{
  Foo() string
}

这样,我们就算定义了一个接口,这个接口需要有方法Foo() string。我们只用定义一个 struct

type foo struct{}

func (f foo) Foo() string {
  return "foo"
}

这样,这个 foo 就算实现了fooInterface 这个接口。

func(a fooInterface) {
  fmt.Println("ok")
}(&foo{}) // "ok"

接口在很多地方都可以应用,比如抽象一个数据库数据对象,或者写一个功能的多种算法实现。都是非常简便的方法。

接口反推类型

有的时候我们需要通过接口来反推类型。举个例子,比如用 yaml 写了一个文件。你需要解析它,读取数据。

	func Unmarshal(in []byte, out interface{}) (err error)

Unmarshal 的接口长这样,out 就是你要传递进去的参数,通常,你可以直接定义一个接口体,传进去,就像 yaml 给出的例子。

type T struct {
    F int `yaml:"a,omitempty"`
    B int
}
var t T
yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)

但是有时候,数据比较多样化,结构顶部下来的时候,就可以使用接口 + type assertion 的方式来做了。

var t interface{}
yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
fmt.Println(t) // map[a:1 b:2]

我们可以看到,打印出来了一个 map[a:1 b:2]

k := t.(map[string]interface{})

上面这种就是 type assertion 了,就得到了一个 map[string]interface{} 类型的数据了。需要注意的是,如果类型不匹配的话, 程序是会 panic 的。为了避免 panic

k, ok := t.(map[string]interface{})

加上一个 ok 就可以了。通过判断 ok 的值,可以判断是否成功。

embedded + interface

golang 的 struct 的 field 可以是 embeded 的。

type bar struct {
  foo
}

通过 embedded 引入 struct 的成员比较有意思,如果 embedded 实现了一个借口,那么这个 struct 同样实现了这个接口。这个特性也比较有意思。 灵活使用的话,可以省很多事情。


介绍就这样了,这一周写 cms 用到的一些特性。很久没写上层的业务,发现上层的业务还是蛮考验代码功底的。