记得刚从Java转Go的时候,一个用Go语言的前辈告诉我:“要少用interface{},这玩意儿很好用,但是最好不要用。”那时候我的组长打趣接话:“不会,他是从Java转过来的,碰到个问题就想定义个类。”当时我对interface{}的第一印象也是类比Java中的Object类,我们使用Java肯定不会到处去传Object啊。后来的事实证明,年轻人毕竟是年轻人,看着目前项目里漫天飞的interface{},它们时而变成函数形参让人摸不着头脑;时而隐藏在结构体字段中变化无穷。不禁想起以前看到的一句话:“动态语言一时爽,重构代码火葬场。”故而写下此篇关于interface{}的经验总结,供以后的自己和读者参考。
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:
fmt.Println(v)
case BlankQuestion:
fmt.Println(v)
case nil:
fmt.Println(v)
}
fmt.Println(data)
}
}
// ------- 输出--------
{
{1001 CHOICE} [A B]}
data - &{
{1001 CHOICE} [A B]}
EN,好像通过Go的switch-type语法糖,判断起来也不是很复杂嘛。如果你也这样以为,并且跟我一样用了这个方法,恭喜你已经入坑了。
因为需求永远是多变的,假如现在有个需求,需要在ChoiceQuesiton打印时,给它的QuestionContent字段添加前缀选择题

本文探讨了在Go语言中使用interface{}的潜在问题,包括对象转型的坑和误用导致的非预期行为。文章通过实例解释了如何避免这些陷阱,强调了良好接口设计的重要性,提醒开发者在使用interface{}时需谨慎思考,以保持代码的清晰和高效。
最低0.47元/天 解锁文章
1516





