Go函数闭包实战-复用函数

Go Web 编程实战:利用闭包和函数字面量消除重复代码

在构建 Web 应用时,我们经常会发现自己在不同的处理函数(Handler)中编写相同的逻辑。比如,验证请求路径、提取参数、处理错误等。

在之前的代码中,我们的 viewHandler、editHandler 和 saveHandler 都必须在开头调用 getTitle 来获取页面标题并进行错误检查。这种重复代码不仅显得臃肿,而且一旦验证逻辑发生变化,我们需要修改所有的地方。

那么,如果我们能把这些通用的验证逻辑"包装"起来,只写一次,该多好?

Go 语言的 函数字面量 (Function Literals) 和 闭包 (Closures) 正是解决这个问题的利器。

1. 基础工作:使用正则表达式进行安全验证

在优化代码结构之前,我们先解决一个严重的安全隐患:用户可能会提供任意路径来读取或写入服务器上的文件。

为了防止这种情况,我们需要验证请求的 URL 是否合法。我们引入 regexp 包,并定义一个全局变量 validPath:

Go

复制代码
import "regexp"

// 预编译正则表达式,如果编译失败会直接 Panic
var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")

这里使用 MustCompile 而不是 Compile,是因为正则表达式是硬编码的,如果编译失败说明代码有问题,程序不应该启动。

接下来,我们需要一个辅助函数来验证路径并提取标题:

Go

复制代码
func getTitle(w http.ResponseWriter, r *http.Request) (string, error) {
    m := validPath.FindStringSubmatch(r.URL.Path)
    // 如果匹配失败(m 为 nil),说明路径非法
    if m == nil {
        http.NotFound(w, r)
        return "", errors.New("invalid Page Title")
    }
    return m[2], nil // 返回正则匹配到的第二个子表达式(即标题部分)
}

虽然这样解决了安全问题,但我们必须在每个 Handler 里都调用一遍 getTitle,代码重复率很高。接下来,让我们用闭包来重构它。

2. 重构目标:改变 Handler 的签名

首先,我们需要调整现有的处理函数。既然我们要把"提取 Title"的逻辑抽离出去,那么这些 Handler 就不再需要自己去解析 Title,而是应该直接接收 Title 作为参数。

我们将函数的定义从标准的 (w, r) 修改为包含 title 的形式:

Go

复制代码
func viewHandler(w http.ResponseWriter, r *http.Request, title string)
func editHandler(w http.ResponseWriter, r *http.Request, title string)
func saveHandler(w http.ResponseWriter, r *http.Request, title string)

3. 核心武器:编写包装函数 (Wrapper Function)

现在,我们需要一个"中间人"。这个函数接收上面那种包含 title 参数的函数,然后返回一个标准的 http.HandlerFunc(即 func(http.ResponseWriter, *http.Request)),以便能被 http.HandleFunc 调用。

我们把这个包装函数命名为 makeHandler:

Go

复制代码
func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // 1. 在这里统一进行正则验证和 Title 提取
        m := validPath.FindStringSubmatch(r.URL.Path)
        
        // 2. 如果验证失败,直接返回 404,不再执行后续逻辑
        if m == nil {
            http.NotFound(w, r)
            return
        }
        
        // 3. 验证通过,调用真正的业务逻辑 fn,并将提取出的 Title (m[2]) 传进去
        fn(w, r, m[2])
    }
}

什么是闭包?

上面代码中返回的那个匿名函数就是一个闭包 。因为它在函数体内引用了定义在它外部的变量 fn。

在这个例子中,fn 变量(即我们在调用 makeHandler 时传入的 viewHandler 或 saveHandler)被这个闭包"捕获"并封闭在内部。无论这个闭包在哪里被调用,它都能访问到当初传入的那个 fn。

4. 在 Main 函数中应用

有了 makeHandler,我们的 main 函数变得更加语义化了。我们在注册路由时,直接用 makeHandler 包裹我们的业务逻辑:

Go

复制代码
func main() {
    // 使用 makeHandler 包装具体的业务逻辑
    http.HandleFunc("/view/", makeHandler(viewHandler))
    http.HandleFunc("/edit/", makeHandler(editHandler))
    http.HandleFunc("/save/", makeHandler(saveHandler))

    log.Fatal(http.ListenAndServe(":8080", nil))
}

5. 享受成果:更纯粹的业务逻辑

最后,我们可以移除具体 Handler 中所有关于 getTitle 的调用和错误检查代码。现在的 Handler 只关注具体的业务逻辑(显示、编辑或保存),变得非常清爽:

Go

复制代码
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 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)
}

总结

通过使用闭包,我们成功实现了一种类似"中间件"或"装饰器"的模式:

  1. 解耦:将通用的验证逻辑(URL 解析、错误处理)与具体的业务逻辑分离。

  2. 复用 :验证代码只写了一次(在 makeHandler 中),却应用到了所有的 Handler 上。

  3. 安全:如果 URL 不合法,请求在进入业务逻辑之前就会被拦截。

这展示了 Go 语言函数式编程特性的强大之处,让代码更简洁、更易维护。

相关推荐
我就是全世界1 小时前
OpenCode Go 套餐划算吗:先看你用哪个模型
开发语言·后端·golang
茶底世界之下1 小时前
灯暖窗冷,色温要不要继续降?用 Awaking 看混合光的取舍
ios
小杨互联网4 小时前
Go 现代 CLI 工具离线镜像合集
开发语言·后端·golang·开源·go·命令行
2501_916007476 小时前
Windows OpenSSL 生成 iOS 证书,制作 p12 与描述文件,KXBuild 签名实操
windows·vscode·ios·objective-c·个人开发·swift·敏捷流程
microrain7 小时前
跟着一条属性报文走一遍 SagooIoT 的接入链路
物联网·架构·golang·sagooiot
CodeGL7 小时前
微服务框架——go-zero
微服务·架构·golang
最强小杰8 小时前
gpt-image-2.5-sunburst + OpenAI SDK + Cline 怎么接?完整图生图与局部重绘教程
gpt·ios·ai·iphone
kk_net88998 小时前
FSNotes:macOS 和 iOS 上的开源笔记管理器
笔记·其他·macos·ios
00后程序员张9 小时前
Windows 开发 iOS App 入门,用 SwiftUI 写第一个 iOS App 装到 iPhone
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
2501_9151063218 小时前
抓包抓到乱码或压缩字节怎么解析,TraceEagle五种查看模式和一键解码
网络协议·计算机网络·网络安全·ios·adb·https·udp