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 语言函数式编程特性的强大之处,让代码更简洁、更易维护。

相关推荐
Digitally2 小时前
如何轻松地将大型音频文件从 iPhone 发送到不同的设备
ios·iphone
catchadmin3 小时前
PHP 现在可以零成本构建原生 iOS 和 Android 应用 NativePHP for Mobile v3 发布
android·ios·php
TheNextByte14 小时前
如何将照片从 iPhone 传输到三星?
ios·iphone
Z.风止4 小时前
Go-learning(1)
开发语言·笔记·后端·golang
2501_915921434 小时前
不用 Xcode 上架 iOS,拆分流程多工具协作完成 iOS 应用的发布准备与提交流程
android·macos·ios·小程序·uni-app·iphone·xcode
Ron丶4 小时前
iOS 旧版本 App 下载方法汇总:如何获取历史版本 IPA(2026 仍有效)
windows·经验分享·macos·ios·电脑
小二·5 小时前
Go 语言系统编程与云原生开发实战(第7篇)分布式系统核心能力:配置中心 × 链路追踪 × 熔断降级(生产级落地)
开发语言·云原生·golang
晴天无痕5 小时前
ios OC 获取当前控制器
ios
lead520lyq5 小时前
Golang Grpc接口调用实现账号密码认证
开发语言·后端·golang
小二·5 小时前
Go 语言系统编程与云原生开发实战(第6篇)云原生部署实战:Docker 镜像瘦身 × K8s 部署 × Helm 一键发布
docker·云原生·golang