interface是一种类型,可以理解为interface跟int,float,struct都是同一类,都是类型,是一种抽象的类型。interface是一组方法的集合,是duck-type programming的一种体现,接口做的事情就像是定义一个协议(规则)。
在C++中,我们会在类中声明类函数,然后要求这个类函数必须实现,不然会在编译时报错。Go的interface有些类似,但是interface不要求必须实现。首先看看interface的定义:
type Handler interface {
Get() int
}
Go 语言虽然不是严格意义上的面向对象语言,但是接口的引入为它带来了动态多态这一特性,调用接口类型的方法时,如果编译期间不能确认接口的类型,Go 语言会在运行期间决定具体调用该方法的哪个实现。
Go 语言使用 runtime.iface 表示第一种接口,使用 runtime.eface 表示第二种不包含任何方法的接口 interface{},即空接口。两种接口虽然都使用 interface 声明,但是由于后者在 Go 语言中很常见,所以在实现时使用了特殊的类型。
interface的一个重要应用是作为函数参数传入函数,好比是c往函数参数传入函数指针,或者C++往函数参数传入基类指针,实现动态多态。Go要做到类似的效果可以采取interface的方式,个人认为Go的interface方式更为优雅易于理解。
如果一个类型实现了一个 interface 中所有方法,我们说类型实现了该 interface。如果定义一个函数参数是 interface{} 类型,不带任何方法的interface也就是空接口类型,那这个函数可以接受任何类型作为它的参数。下面开始介绍空接口几个常用的场景。
空接口作函数参数可以接受任意类型的数据
使用空接口实现可以接收任意类型的函数参数。
package main
// main.go
import (
"fmt"
)
func handler() {
}
func HttpHandler(v interface{}){
fmt.Println(v)
}
type request struct {
uid int
cmd int
}
func main() {
m := make(map[string]interface{})
m["name"] = "James"
m["age"] = 20
m["slice"] = make([]int, 3)
m["map"] = make(map[int]int, 3)
m["fun"] = HttpHandler
m["req"] = request{1,2}
HttpHandler("James")
HttpHandler(20)
HttpHandler(make(map[int]int, 3))
HttpHandler(request{1,2})
HttpHandler(make([]int, 3))
HttpHandler(handler)
}
输出如下,不管函数参数传什么类型的值,都能正常接收和打印,没有报编译错误。
James
20
map[]
{1 2}
[0 0 0]
0x109f380
空接口作map的value值可以存储任意类型的数据
使用空接口实现可以保存任意值的字典,观察以下这个例子:
package main
// main.go
import (
"fmt"
)
func HttpHandler(v interface{}){
}
type request struct {
uid int
cmd int
}
func main() {
m := make(map[string]interface{})
m["name"] = "James"
m["age"] = 20
m["slice"] = make([]int, 3)
m["map"] = make(map[int]int, 3)
m["fun"] = HttpHandler
m["req"] = request{1,2}
fmt.Println(m)
}
输出如下,不管map的value是什么类型的数据,都能存储下来且不会编译错误。
map[age:20 fun:0x109f380 map:map[] name:James req:{1 2} slice:[0 0 0]]
空接口的类型断言
我们可以对空接口进行类型判断,如果是我们所期望的数据结构,那么可以继续使用该数据结构进行后面的逻辑,如果不是就打印错误幷提前返回。
var x interface{}
x = "hello go"
v, ok := x.(string)
if ok {
fmt.Println(v)
} else {
fmt.Println("类型断言失败")
}
空接口的综合应用
下面这个例子,结合了上面三个空接口的特性,模仿http server实现了简单的Get/Post方法处理:
- 函数参数为空接口类型:
type HandlerFunc func(req interface{}, rsp interface{})
- 变量定义为空接口类型:
var req interface{}
- 在handler函数中做空接口类型判断
req, ok := r.(*requestQuery);
这里的最重要的一行代码是handler(req, rsp)
,我们可以往这个handler传入任意类型的req和rsp,因此可以实现任意处理函数,实现了动态多态。
package main
// main.go
import (
"fmt"
"time"
)
type HandlerFunc func(req interface{}, rsp interface{})
type HttpHandler struct {
router map[string]HandlerFunc
}
func New() *HttpHandler {
return &HttpHandler{make(map[string]HandlerFunc)}
}
func (h *HttpHandler)addRoute(method string, partten string, handler HandlerFunc) {
key := method + "-" + partten
h.router[key] = handler
}
func (h *HttpHandler) Get(pattern string, handler HandlerFunc) {
h.addRoute("GET", pattern, handler)
}
func (h *HttpHandler) Post(pattern string, handler HandlerFunc) {
h.addRoute("POST", pattern, handler)
}
type requestCalc struct {
Id int
Val int
}
type responseCalc struct {
Id int
Val int
}
type requestQuery struct {
Id int
Val int
}
type responseQuery struct {
Id int
Val int
}
func CalcData(r interface{}, s interface{}) {
req, ok := r.(*requestCalc); if !ok {
fmt.Println("requestCalc类型断言失败")
return
}
rsp, ok := s.(*responseCalc); if !ok {
fmt.Println("responseCalc类型断言失败")
return
}
rsp.Id = req.Id
rsp.Val = req.Val + 1
}
func QueryData(r interface{}, s interface{}) {
req, ok := r.(*requestQuery); if !ok {
fmt.Println("requestQuery类型断言失败")
return
}
rsp, ok := s.(*responseQuery); if !ok {
fmt.Println("responseQuery类型断言失败")
return
}
rsp.Id = req.Id
rsp.Val = req.Val + 2
}
func main() {
h := New()
h.Get("query_data", QueryData)
h.Post("calc_data", CalcData)
for {
var req interface{}
var rsp interface{}
time.Sleep(time.Second * 1)
var key string
if time.Now().Unix() % 2 == 0 {
key = "POST-calc_data"
req = &requestCalc{1,100}
rsp = &responseCalc{}
} else {
key = "GET-query_data"
req = &requestQuery{2,200}
rsp = &responseQuery{}
}
if handler, ok := h.router[key]; ok {
handler(req, rsp)
fmt.Printf("req:%+v, rsp:%+v\n", req, rsp)
} else {
fmt.Printf("404 NOT FOUND: %s\n", key)
}
}
}
输出如下:
req:&{Id:2 Val:200}, rsp:&{Id:2 Val:202}
req:&{Id:1 Val:100}, rsp:&{Id:1 Val:101}
req:&{Id:2 Val:200}, rsp:&{Id:2 Val:202}
req:&{Id:1 Val:100}, rsp:&{Id:1 Val:101}
req:&{Id:2 Val:200}, rsp:&{Id:2 Val:202}
req:&{Id:1 Val:100}, rsp:&{Id:1 Val:101}
req:&{Id:2 Val:200}, rsp:&{Id:2 Val:202}
req:&{Id:1 Val:100}, rsp:&{Id:1 Val:101}
req:&{Id:2 Val:200}, rsp:&{Id:2 Val:202}
req:&{Id:1 Val:100}, rsp:&{Id:1 Val:101}
req:&{Id:2 Val:200}, rsp:&{Id:2 Val:202}