记得刚从Java转Go的时候,一个用Go语言的前辈告诉我:“要少用interface{},这玩意儿很好用,但是最好不要用。”那时候我的组长打趣接话:“不会,他是从Java转过来的,碰到个问题就想定义个类。”当时我对interface{}的第一印象也是类比Java中的Object类,我们使用Java肯定不会到处去传Object啊。后来的事实证明,年轻人毕竟是年轻人,看着目前项目里漫天飞的interface{},它们时而变成函数形参让人摸不着头脑;时而隐藏在结构体字段中变化无穷。不禁想起以前看到的一句话:“动态语言一时爽,重构代码火葬场。”故而写下此篇关于interface{}的经验总结,供以后的自己和读者参考。
-
-
- 1. interface{}之对象转型坑
- 2. interface{}之nil坑
- 3. 总结和引用
-
1. interface{}之对象转型坑
一个语言用的久了,难免使用者的思维会受到这个语言的影响,interface{}作为Go的重要特性之一,它代表的是一个类似*void
的指针,可以指向不同类型的数据。所以我们可以使用它来指向任何数据,这会带来类似与动态语言的便利性,如以下的例子:
type BaseQuestion struct{
QuestionId int
QuestionContent string
}
type ChoiceQuestion struct{
BaseQuestion
Options []string
}
type BlankQuestion struct{
BaseQuestion
Blank string
}
func fetchQuestion(id int) (interface{} , bool) {
data1 ,ok1 := fetchFromChoiceTable(id) // 根据ID到选择题表中找题目,返回(ChoiceQuestion)
data2 ,ok2 := fetchFromBlankTable(id) // 根据ID到填空题表中找题目,返回(BlankQuestion)
if ok1 {
return data1,ok1
}
if ok2 {
return data2,ok2
}
return nil ,false
}
在上面的代码中,data1是ChoiceQuestion
类型,data2是BlankQuestion
类型。因此,我们的interface{}指代了三种类型,分别是ChoiceQuestion
、BlankQuestion
和nil
,这里就体现了Go和面向对象语言的不同点了,在面向对象语言中,我们本可以这么写:
func fetchQuestion(id int) (BaseQuestion , bool) {
...
}
只需要返回基类BaseQuestion
即可,需要使用子类的方法或者字段只需要向下转型。然而在Go中,并没有这种is-A
的概念,代码会无情的提示你,返回值类型不匹配。
那么,我们该如何使用这个interface{}
返回值呢,我们也不知道它是什么类型啊。所以,你得不厌其烦的一个一个判断:
func printQuestion(){
if data, ok := fetchQuestion(1001); ok {
switch v := data.(type) {
case ChoiceQuestion:
(v)
case BlankQuestion:
(v)
case nil:
(v)
}
(data)
}
}
// ------- 输出--------