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

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

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

在之前的代码中,我们的 viewHandlereditHandlersaveHandler 都必须在开头调用 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 时传入的 viewHandlersaveHandler)被这个闭包"捕获"并封闭在内部。无论这个闭包在哪里被调用,它都能访问到当初传入的那个 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 语言函数式编程特性的强大之处,让代码更简洁、更易维护。

相关推荐
Daniel_Coder20 小时前
iOS Widget 开发-16:Widget 网络数据加载策略
ios·swift·widget·widgetcenter
XMYX-021 小时前
34 - Go 二进制处理(编码/解码)深度解析
开发语言·golang
美狐美颜SDK开放平台21 小时前
美颜SDK开发详解:如何优化美颜SDK在低端安卓机上的性能?
android·ios·音视频·直播美颜sdk·视频美颜sdk
Kurisu57521 小时前
FilzaCracked_4.0.0_TS.ipa2026最新官方正版免费下载 一键转存 永久更新 (看到速转存 资源随时走丢)手机版通用
ios·智能手机·电脑·巨魔
恣艺21 小时前
用Go从零实现一个高性能KV存储引擎:B+Tree索引、WAL持久化、LRU缓存的工程实践
开发语言·数据库·redis·缓存·golang
geovindu1 天前
go: Semaphore Pattern
开发语言·后端·设计模式·golang·企业级信号量模式
ACP广源盛139246256731 天前
iOS 27 开放 AI 生态@ACP#小型化扩展黄金风口,IX8008全面超越 ASM2806,铸就嵌入式 AI 扩展核心
人工智能·嵌入式硬件·macos·ios·计算机外设·objective-c·cocoa
人月神话Lee1 天前
【图像处理】卷积原理与卷积核——图像处理的核心引擎
ios·ai编程·图像识别
用户223586218202 天前
如何在超大型的工程中使用 Claude Code?
前端·ios·claude
dusk_star2 天前
go语言--笔记--封装、组合(继承)
笔记·golang