go之闭包及其应用

https://www.calhoun.io/5-useful-ways-to-use-closures-in-go/

5种有用的闭包

在本文中,我们将探索闭包和匿名函数的几种不同的实际用例,以便您能够更好地理解闭包何时是合适的,并了解它们如何应用于不同的情况。

1.隔离数据

假设您想创建一个函数,该函数可以访问即使在函数退出之后仍然存在的数据。例如,您想要计算函数被调用了多少次,或者您想要创建一个fibonacci数字生成器,但是您不想让任何人访问该数据(这样他们就不会意外地更改它)。您可以使用闭包来实现这一点。

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6   gen := makeFibGen()
 7   for i := 0; i < 10; i++ {
 8     fmt.Println(gen())
 9   }
10 }
11 
12 func makeFibGen() func() int {
13   f1 := 0
14   f2 := 1
15   return func() int {
16     f2, f1 = (f1 + f2), f2
17     return f1
18   }
19 }
View Code

当然,您可以使用自定义类型来创建非常类似的东西,但是如果您希望使用多个数字生成器,您可能最终需要声明一个接口,并将其作为使用生成器的其他函数的参数接受,比如这样。

1 type Generator interface {
2   Next() int
3 }
4 
5 func doWork(g Generator) {
6   n := g.Next()
7   fmt.Println(n)
8   // ... do work with n
9 }
View Code

使用闭包,您可以只要求函数作为参数传入,因为无论如何,您实际上只关心生成器接口中的一个方法。

func doWork(f func() int) {
  n := f()
  fmt.Println(n)
  // ... do work with n
}

2.封装函数并创建中间件

函数是Go语言的一等公民,这意味着您不仅可以动态地创建匿名函数,还可以将函数作为参数传递给函数。例如,在创建web服务器时,通常提供一个函数来处理特定路由的web请求。

package main

import (
  "fmt"
  "net/http"
)

func main() {
  http.HandleFunc("/hello", hello)
  http.ListenAndServe(":3000", nil)
}

func hello(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintln(w, "<h1>Hello!</h1>")
}

虽然这段代码不需要闭包,但是如果我们想用更多的逻辑来包装处理程序,闭包是非常有用的。一个完美的例子是,当我们想要在处理程序执行之前或之后创建中间件来执行工作时。

什么是中间件:

中间件基本上是可重用函数的一个花哨术语,可在设计用于处理web requst的代码之前和之后运行代码。在Go中,这些通常是用闭包来完成的,但是在不同的编程语言中,可以用其他方法来完成。 

在编写web应用程序时,使用中间件是很常见的,它们不仅仅对计时器有用(您将在下面看到一个示例)。例如,可以使用中间件编写代码来验证用户是否登录过一次,然后将其应用于所有仅限成员的页面。

package main

import (
  "fmt"
  "net/http"
  "time"
)

func main() {
  http.HandleFunc("/hello", timed(hello))
  http.ListenAndServe(":3000", nil)
}

func timed(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
  return func(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    f(w, r)
    end := time.Now()
    fmt.Println("The request took", end.Sub(start))
  }
}

func hello(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintln(w, "<h1>Hello!</h1>")
}

注意,我们的timing()函数接受一个可以用作处理函数的函数,并返回一个类型相同的函数,但是返回的函数与传递它的函数不同。返回的闭包记录当前时间,调用原始函数,最后记录结束时间并打印出请求的持续时间。同时不知道处理函数内部实际发生了什么。

3.访问通常不可用的数据

虽然这使用了我们在本文前面看到的技术,但值得指出它本身,因为它非常有用。

闭包还可以用于将数据包装在函数内部,否则该函数通常不能访问该数据。例如,如果要在不使用全局变量的情况下提供对数据库的处理程序访问,则可以编写如下代码。

package main

import (
  "fmt"
  "net/http"
)

type Database struct {
  Url string
}

func NewDatabase(url string) Database {
  return Database{url}
}

func main() {
  db := NewDatabase("localhost:5432")

  http.HandleFunc("/hello", hello(db))
  http.ListenAndServe(":3000", nil)
}

func hello(db Database) func(http.ResponseWriter, *http.Request) {
  return func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, db.Url)
  }
}

现在我们可以编写处理函数,就好像它们可以访问一个Database对象,同时仍然返回一个具有http.HandleFunc()期望签名的函数这允许我们绕过http.HandleFunc()不允许我们传递自定义变量而不诉诸全局变量或任何类型的事实。

4.使用排序包进行二进制搜索

在标准库中使用包时也经常需要关闭,例如排序包。

这个包为我们提供了大量有用的函数和代码,用于排序和搜索排序列表。例如,如果要对整数切片进行排序,然后在切片中搜索数字7,则可以sort像这样使用包。

package main

import (
  "fmt"
  "sort"
)

func main() {
  numbers := []int{1, 11, -5, 7, 2, 0, 12}
  sort.Ints(numbers)
  fmt.Println("Sorted:", numbers)
  index := sort.SearchInts(numbers, 7)
  fmt.Println("7 is at index:", index)
}

但是如果要搜索每个元素是自定义类型的切片会发生什么?或者,如果要查找第一个数字7或更高的索引,而不仅仅是第一个7的索引?

为此,您需要使用sort.Search()函数,并且需要传入一个闭包,该闭包可用于确定特定索引处的数字是否符合您的条件。

5.推迟工作

posted on 2019-03-14 14:26  小豆角  阅读(271)  评论(0编辑  收藏  举报

导航