最近对Go语言产生了点兴趣,也正好此玩意可以在eclipse中开发,就下载了玩玩。学习过程中发现Go语言也有闭包这个概念,由于之前自学JS的时候也学过闭包,所以对此特别关注学习了下,也在网上搜索了一些关于Go语言的闭包概念。但是介于golang.org一直进不去,待将来爬进去后再去深刻的恶补下golang的闭包概念.
1、网上常见的三个闭包案例(前两个节选自小谈golang闭包)
案例一、
package main import ( "fmt" ) func main() { var interiorFn[10] func() for i := 0; i < len(interiorFn); i++ { interiorFn[i] = func() { fmt.Printf("定义在函数内部的闭包值:%d\n",i) } } for j := 0; j < len(interiorFn); j++ { interiorFn[j]() } }
案例二、
package main import ( "fmt" ) func main() { var externalFn [10] func() for i := 0; i < len(externalFn); i++ { externalFn[i] = closureFunc(i) } for j := 0; j < len(externalFn); j++ { externalFn[j]() } } func closureFunc(id int) func() { return func() { fmt.Printf("外部定义的闭包值:%d\n",id) } }
案例一:输出
内部闭包值:10
内部闭包值:10
内部闭包值:10
内部闭包值:10
内部闭包值:10
内部闭包值:10
内部闭包值:10
内部闭包值:10
内部闭包值:10
内部闭包值:10
案例二:输出
Golang的闭包实验:
外部闭包值:0
外部闭包值:1
外部闭包值:2
外部闭包值:3
外部闭包值:4
外部闭包值:5
外部闭包值:6
外部闭包值:7
外部闭包值:8
外部闭包值:9
这两个案例的输出值不一样,因还没有看到官方的文档,所以我大胆猜测如下:
案例一:由于闭包定义在main函数里面,for循环中定义的局部变量i对于此闭包而言就等于外部的变量,内部可以访问外部的变量,因此在第二个for循环的时候,闭包执行的fmt.Printf("定义在函数内部的闭包值:%d\n",i)的时候,其实就是打印出main函数中for循环定义的那个局部变量的值,由与变量i已经递增至10,所以在第二个for循环执行闭包的时候,打印出来10个10。这个应该好理解。
案例二:当闭包定义在main函数外面的时候,有closureFunc函数产生闭包的时候,情形就完全与案例一不同了。首先查看closureFunc函数的参数定义,是int类型,属于值传递,在Go中值传递是通过复制的方式传递的。那么在第一个循环中,假设i递增到2,调用closureFunc(i)来获取一个闭包,由于值传递,所以等同于调用了closureFunc(2),closureFunc方法里定义的闭包fmt.Printf("外部定义的闭包值:%d\n",id)打印的id是closureFunc函数里面的id变量,由于外部传递进来的是2,所以id这个变量就变成了2.所以案例二的输出结果就变成如上的形式了。
案例三:把案例二的值传递改成引用传递
package main import ( "fmt" ) func main() { var externalFn [10]func() var context = []int{0} for i := 0; i < len(externalFn); i++ { context[0] = i; externalFn[i] = closureFunc(context) } for j := 0; j < len(externalFn); j++ { externalFn[j]() } } func closureFunc(id[] int) func() { return func() { fmt.Printf("外部闭包值:%d\n",id) } }
输出结果就变成了:
外部闭包值:[9]
外部闭包值:[9]
外部闭包值:[9]
外部闭包值:[9]
外部闭包值:[9]
外部闭包值:[9]
外部闭包值:[9]
外部闭包值:[9]
外部闭包值:[9]
外部闭包值:[9]
2、闭包的作用
上面演示了闭包的代码,闭包的作用很明显,利用函数内部可以访问当前函数定义的外部函数的变量的特性,使其它函数能够获取其它函数的局部变量(受保护变量?)的功能。
比如在closureFunc的局部变量是id,main函数是获取不到这个id这个变量值的,但是通过闭包就可以获取到了。
3、闭包的应用场景参考JS
个人感觉在两种情况下,可以考虑使用闭包
-
-
-
-
- 局部变量驻留内存,不被垃圾回收站给回收掉(比如局部变量要延迟与当前函数调用)
- 局部变量能被外面函数调用
-
-
-
4、一个闭包的使用案例
在阅读Writing Web Applications的时候就看到了一个很典型的闭包应用.对于国内用户如果死活访问不了的用户可以访问用Go语言开发Web程序[翻译]一文,是个热心(大叔大哥?)对官方文档的翻译。
func editHandler(w http.ResponseWriter, r *http.Request, title string) {
p, err := loadPage(title)
if err != nil {
p = &Page{Title: title}
}
renderTemplate(w, "edit", p)
}
func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
body := r.FormValue("body")
p := &Page{Title: title, Body: []byte(body)}
err := p.save()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/view/"+title, http.StatusFound)
}
func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
p, err := loadPage(title)
if err != nil {
http.Redirect(w, r, "/edit/"+title, http.StatusFound)
return
}
renderTemplate(w, "view", p)
}
//闭包的使用
func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
title := r.URL.Path[lenPath:]
if !titleValidator.MatchString(title) {
http.NotFound(w, r)
return
}
fn(w, r, title)
}
}
func main() {
http.HandleFunc("/view/", makeHandler(viewHandler))
http.HandleFunc("/edit/", makeHandler(editHandler))
http.HandleFunc("/save/", makeHandler(saveHandler))
http.ListenAndServe(":7777", nil)
}
上面的代码是抽取出来的一部分,只是用于说明闭包的其中一种典型使用。
editHandler这个函数是主处理业务逻辑。这个业务逻辑中需要对传递进来的参数title进行参数合法性校验。同样saveHandler,viewHandler也有同样的需求,为了追求良好的代码编写,我们希望把title放在一个统一的地方进行校验,所以使用makeHandler来封装editHandler,saveHandler,viewHandler这三个函数,在调用这三个函数前我们希望先对title进行合法性校验.
在上述需求的指导下,大致的设计思路是,在调用editHandler,saveHandler,viewHandler之前首先需要校验title,然后根据用户请求的URL来选择对应的处理函数.
4.1、通常情况下,我们会考虑如下的设计方案(伪代码:
1、在main函数绑定/这样的URL规则统一使用某一个函数来处理,比如A
2、在A里面先获取操作名(view或edit或save)和title
3、校验title是否正确
4、根据操作名来调用对用的handler函数。(switch语句)
上述业务逻辑合适么,当然能满足需求,它把URL的路由规则放到自己的函数里来处理,只是考虑将来如果一些handler不需要去校验title的情况下,那么这些handler的路由规则就需要放到main函数中去(这样路由有两个地方来分配就比较乱了)或者需要修改A函数(较复杂)来适应。总体来说,我们还是希望能够在main函数做路由规则。
4.2 、golang的闭包设计方案
1、设计一个函数B,该函数可以把handler函数当成参数传递进来
2、函数B先校验title,然后执行传递进来的handler函数(有木有java的接口编程赶脚)。
但是这里遇到一个问题就是http.HandleFunc绑定的执行函数只有两个参数w http.ResponseWriter, r *http.Request,所以不能传递额外的参数比如我们需要的执行handler函数名,所以我们考虑使用闭包,将函数B设计成:
//闭包的使用
func B(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//业务逻辑
}
}
这样调用B函数返回了一个http.HandleFunc可以绑定的函数,这个函数我们成为闭包函数C。由于闭包函数C是定义在函数B里面的,所以它可以调用函数B的局部变量也就也是B函数传递进来的参数fn(viewHnalder或editHandler或saveHandler).所以把代码改写成
//闭包的使用
func B(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//业务逻辑
title : = 从URL上获取
fn(w, r, title)
}
}
fn是函数B的局部变量,因为其被闭包函数C给调用了,所以可以使他一直在内存中驻留不被垃圾回收,实际上fn等同于变成了闭包函数C的局部变量了.
func B(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//业务逻辑
}
}
类似于下面这段代码的感觉,功能是一样的
func C(w http.ResponseWriter, r *http.Request,fn func(http.ResponseWriter, *http.Request, string)){
}
只是C是不能被
http.HandleFunc绑定,而B(viewHandler)可以被http.HandleFunc给绑定.
网络参考文章:
JS闭包概念:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
小谈golang闭包:http://blog.youkuaiyun.com/liugao15/article/details/8296064
writing web application:http://golang.org/doc/articles/wiki/