Go语言的闭包学习心得

       最近对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

    个人感觉在两种情况下,可以考虑使用闭包

          1. 局部变量驻留内存,不被垃圾回收站给回收掉(比如局部变量要延迟与当前函数调用)
          2. 局部变量能被外面函数调用

   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的局部变量了.
  4.3、案例总结
    在这个案例中,闭包做了一件最重要的事情就是扩充了 http.HandleFunc绑定的执行函数的参数限制,可以额外的随意添加任意多的参数,利用闭包功能来让这些参数的生命周期延长到与闭包函数一样长,等同于这些参数变成了闭包函数的局部变量.所以:
    
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给绑定.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值