Interface
是 Go 语言中及其重要的概念,可以从作为接口约定
、作为类型值
两个方面去理解它。
作为接口约定
接口,可以理解是一种约定俗成,便于各系统间的沟通、协作。
比如不同品牌的显示器要与不同品牌的显卡进行数据交互,若大家都按照自己的方式来,就会乱套。因此定义好 接口
规范,即两个部件间交互的方式,这样不同系统间才能相互协作。比如显示器与显卡间就有 HDMI、VGA 等接口协议。
同样的,在 Go 中使用 interface
关键字,定义好接口规范,方便不同类型的对象间的交互。
例如,Go 标准库中的 fmt
库,定义了 Stringer
的 interface
,这个接口定义了一个方法 String() string
。
1
2
3
4
5
|
package fmt
type Stringer interface {
String() string
}
|
任何结构体对象只要实现了 String() string
的方法,就可以说该结构体实现了 Stringer
,即可使用 fmt.Print
系列方法,自定义该结构体对象的输出结果。
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
26
27
28
29
30
|
type MyStruct1 struct {
Name string
Age int
}
func (m *MyStruct1) Stringer() string {
return fmt.Sprintf("Name:%s, Age:%d", m.Name, m.Age)
}
type MyStruct2 struct {
CurrentHour int
}
func (m *MyStruct2) Stringer() string {
return fmt.Sprintf("Current Time is %d:00", m.CurrentHour)
}
func main() {
var s Stringer
s = MyStruct1{
Name: "aaa",
Age: 5
}
fmt.Println(s)
s = MyStruct2{
CurrentHour: 18
}
fmt.Println(s)
}
|
输出为
1
2
|
Name:aaa, Age:5
Current Time is 18:00
|
可看到, Stringer
类型的变量 s
,可承载 MyStruct1
、 MyStruct2
两种结构体对象的值,因为他们都实现了 Stringer
这个 interface
中的所有方法 。
在 fmt.Println()
方法内部,会检查传入的对象是否实现 Stringer
,若实现了,则调用对象自己的 String() string
方法,从而达到了自定义 Println()
输出格式的功能。
作为类型值
既然对象须实现 interface
中声明的所有方法,才算是实现了该 interface
。那么空的 interface
是不是就能被所有结构体对象所实现?
事实上确实如此。interface{}
类型的变量可接受任何类型的值。
1
2
3
4
5
6
7
8
9
|
func main() {
var i interface{}
i = 5
i = "aaa"
i = 6.0
fmt.Println(i)
}
|
在以上代码中,即使 i 被不同类型的数据赋值,但不会报错。
interface{}
经常作为函数参数使用,使用时常常搭配 switch类型断言 来判断 interface{}
变量的具体类型。
1
2
3
4
5
6
7
8
9
10
11
12
|
func PrintType(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("%d is int type", v)
case *int:
fmt.Println("%d is *int type", *v)
case string:
fmt.Println("%s is string type", v)
default:
fmt.Println("other type")
}
}
|
nil 值
其实,interface
的 作为接口约定
、作为类型值
,其实可归为一类,他们的区别是是否有定义方法。对 nil
的判断是不区分这两种情况的。
对于 interface
对象,只有当其类型和值均为 nil
时,它才为 nil
。nil
的判断与函数形参类型无关,只与对象本身有关。例如
1
2
3
4
5
6
7
8
9
10
11
|
func f1(out io.Writer) {
if out != nil {
fmt.Println("a")
}
}
func main() {
var buf *bytes.Buffer
f1(buf)
f1(nil)
}
|
var buf *bytes.Buffer
定义了 类型为 *bytes.Buffer
值为 nil
的对象,因此 buf != nil
的值为 true
,f1(buf)
函数会打印出 a
。
而 nil
对象的类型和值均为 nil
,因此 f1(nil)
不会打印出 a
。
所以最终的输出为
再来一个类似的例子
1
2
3
4
5
6
7
8
9
10
11
|
func f2(out interface{}) {
if out != nil {
fmt.Println("a")
}
}
func main() {
var buf *bytes.Buffer
f2(buf)
f2(nil)
}
|
输出为
分析原因同上面的例子,就不赘述了。
其他
Go 在 1.17 中,引入了泛型。这里不细说,仅举个例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
type Number interface{
type int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64,
float32, float64
}
func max[T Number](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(max(1, 2))
}
|
而在 Go 1.18 中,又有与 interface
相关的改动。即用 any
关键字替代 interface{}
,这样写起来更简洁明了。
1
2
|
func Print[T interface{}](t T) // before
func Print[T any](t T) // after
|
例子
接下来两个例子,演示下 interface 的用法。
标准库 io.Writer
首先,标准库中定义了 Writer
的 interface
1
2
3
|
type Writer interface {
Write(p []byte) (n int, err error)
}
|
我们的样例代码如下
1
2
3
4
5
6
7
|
func main() {
var buf bytes.Buffer
fmt.Fprint(&buf, "a", "b", 1)
fmt.Println(buf.String())
}
|
输出为 ab1
bytes.Buffer
结构体,实现了 io.Writer
这个 interface
,且 fmt.Fprint
函数内部调用了 .Write
方法,因此通过 fmt.Fprint
方法就能直接把字符串写入到另一个库的对象 bytes.Buffer
中。
生成条件嵌套的语句
生成嵌套类型的条件。类似于 SQL 的 where 条件,比如 a=1 and (b=2 or c=3)
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
type Stringer interface {
String() string
}
type Equation struct {
A string
B string
}
func (e Equation) String() string {
return fmt.Sprintf("%s=%s", e.A, e.B)
}
type Condition struct {
Operation string
SubConditions []Stringer
}
func (cond Condition) String() string {
var subConds []string
for _, v := range cond.SubConditions {
subConds = append(subConds, v.String())
}
return fmt.Sprintf("(%s)", strings.Join(subConds, " "+cond.Operation+" "))
}
func main() {
a1 := Equation{A: "a", B: "b"}
a2 := Equation{A: "c", B: "d"}
a3 := Condition{
Operation: "and",
SubConditions: []Stringer{a1, a2},
}
a4 := Condition{
Operation: "or",
SubConditions: []Stringer{a1, a3},
}
fmt.Println(a4.String())
}
|
输出为 (a=b or (a=b and c=d))
参考
go中的接口与多态
Go语言多态的实现与interface使用
在 Go 语言中,有时 nil 并不是一个 nil
Go语言泛型的进化