Interface 基本使用
// _Interfaces_ are named collections of method
// signatures.
package main
import "fmt"
import "math"
// Here's a basic interface for geometric shapes.
type geometry interface {
area() float64
perim() float64
}
// For our example we'll implement this interface on
// `rect` and `circle` types.
type rect struct {
width, height float64
}
type circle struct {
radius float64
}
// To implement an interface in Go, we just need to
// implement all the methods in the interface. Here we
// implement `geometry` on `rect`s.
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
// The implementation for `circle`s.
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
// If a variable has an interface type, then we can call
// methods that are in the named interface. Here's a
// generic `measure` function taking advantage of this
// to work on any `geometry`.
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
// The `circle` and `rect` struct types both
// implement the `geometry` interface so we can use
// instances of
// these structs as arguments to `measure`.
measure(r)
measure(c)
}
这个例子的官方解释是:
矩形和圆形都实现了几何图形的接口
我来做几点解释:
- measure 的参数是一个 geometry 的接口(interface)
- 当调用 measure 函数的时候会有一次类型转换,将实参转换为形参接口类型
- 这个转换过程是在编译期间完成的,编译器会检测方法列表,当实参方法列表是形参方法列表的超集时,此次转换成功
空 Interface
上面那个例子是有方法的interface作为参数,但是很多时候还会出现一种空interface
空interface(interface{})不包含任何的method,因此所有的类型都实现了空interface。
https://tiancaiamao.gitbooks.io/go-internals/content/zh/07.2.html interface 原理
因此空Interface就很像C语言里面的空指针。
comma-ok
但是不同的是,接口包含的是方法。并且空接口也不是直接强制转换为其他接口的,而是通过如下方法:
func Disconnect(usb interface{}){ //注意,这里是空接口
switch v:=usb.(type) {
case PhoneConnect:
fmt.Println(" Phone device Disconnected from",v.name)
case TVConnect:
fmt.Println("TV device Disconnected from",v.name)
default:
fmt.Println("Unknown device ...")
}
}
func main(){
a := PhoneConnect{"IPhone"}
b := TVConnect{"ChuangWei"}
Disconnect(a)
Disconnect(b)
}
这种方式被称为Comma-ok断言
Comma-ok断言的语法是:value, ok := element.(T)。element必须是接口类型的变量,T是普通类型
如果element是T类型的数据,那么断言成功(interface{} 的 _type 字段匹配),转换为value对象。否则OK被置为false
还有一种switch语法就如同上面例子中使用的一样。
interface slice
interface slice 与 万能类型 empty interface 是不一样的,可以直接将任何类型的值传给万能类型,但是不能将任何类型的 slice 直接传给 interface slice
举例说明:
func MethodTakeinSlice(in []interface{}){...}
...
slice := []int{1,2,3}
MethodTakeinSlice(slice) // 会报错 cann't use slic (type []int) as type []interface{}
marshal
一个简单的介绍 https://www.ogeek.net/article/130778.htm
http://cizixs.com/2016/12/19/golang-json-guide
使用过程中需要注意的几点:
- marshal 过程中,对应的struct结构体必须首字母大写!
- unmarshal 函数传递的是 地址!需要加上&符号
记一次 json marshal 过程
某次测试的过程中遇到这样一个问题:
我们的某个接口,访问网页,获取到了返回值,将其 unmarshal 为了一种特殊的结构体
req, err := http.NewRequest(method, t.endpoint+urlPath, bytes.NewReader(bodyData))
resp, err := t.client.Do(req) // resp 为 *http.Response 类型
//type Response struct {
// Code int `json:"code"`
// Message string `json:"message"`
// Data interface{} `json:"data"` // api-specified response result
//}
res := new(hh.Response)
err = hh.UnmarshalStream(marshaler.NewJsonMarshaler(), resp.Body, res)
// res 为 Response 类型
然后我需要将 res.Data 转换成我需要的结构体数组类型。
一开始试来试去,弄出一种方法:
es_status := types.ES_health{}
json.Unmarshal([]byte(resp.Data.(string)), &es_status)
也就是先将 resp.Data 声明为string(居然没有报错),然后转换为 []byte,最后unmarshal。
这个方法对于这个例子还是OK的。
但是,当返回值并不是直接的结构体,而是结构体数组的时候,这种方法就行不通了。
例如:
var val []model.Flavor
json.Unmarshal([]byte(resp.Data.(string)), &val)
报错
panic: interface conversion: interface {} is map[string]interface {}, not string
省去分析过程了,直接上结论
结论
marshal 会将 []byte 代表的 json 数据转换为我们想要的结构体,但是一旦我们转换的目标结构体中包含 interface{} 字段的时候,marshal 便无法分析这段数据的类型,将其转换为默认的 map[string]interface{}。也就是这个 interface{} 数据指针的 _type 成员未被设置。因此之后的 comma-ok 断言会报错。
解决的方式有两种:
- 将其作为 map[string]interface{} 使用,并且调用库mapstructure(需要下载)将 interface{} 转换成我们需要的结构体
for _, v := range res.Data.([]interface{}){
val2 := model.Flavor{}
if err := mapstructure.Decode(v, &val2); err != nil {
t.Fatal(err)
}
t.Log(val2)
}
- 先将这个 interface{} 使用marshal 函数转换为 []byte,然后再调用 unmarshal 将其转换为我们需要的 struct,也就是说,unmarshal 的时候传入我们的 struct 变量,这样unmarshal 函数就会帮我们填充 _type 字段了
var val []model.Flavor
v, err:= json.Marshal(res.Data)
if err!= nil {
t.Fatal(err)
}
json.Unmarshal(v, &val)
t.Log(val)
由于第一种方法会引入额外的依赖,因此第二种方式更优一点。
我觉得应该有直接修改 interface{} 的_type 字段的方法(就像强行转换空指针,虽然感觉很危险),留待以后学习吧。
参考链接:
Go语言Interface漫谈
GO语言Comma-ok断言
|
请发表评论