Go 函数装饰器模式教程

装饰器在 Python 和 TypeScript 等其他编程语言中肯定更为突出 , 但这并不是说你不能在 Go 中使用它们 。事实上 , 对于某些问题 , 使用装饰器是我们希望在本教程中找到的完美解决方案 。
了解装饰器模式

装饰器本质上允许您包装现有功能并在顶部附加或添加您自己的自定义功能 。
【Go 函数装饰器模式教程】在 Go 中 , 函数被视为第一类对象 , 这本质上意味着您可以像传递变量一样传递它们 。让我们用一个非常简单的例子来看看这个:
package mainimport ("fmt""time")func myFunc() {fmt.Println("Hello World")time.Sleep(1 * time.Second)}func main() {fmt.Printf("Type: %Tn", myFunc)}所以 , 在这个例子中 , 我们定义了一个名为 的函数myFunc , 它简单地打印出Hello World 。然而 , 在我们的main()函数体中 , 我们已经调用fmt.Printf并且我们习惯于%T打印出我们作为第二个参数传入的值的类型 。在这种情况下 , 我们正在传递myFunc , 随后将打印出以下内容:
$ go run test.goType: func()那么 , 这对我们 Go 开发人员意味着什么?好吧 , 它强调了函数可以在我们代码库的其他部分中传递并用作参数的事实 。
让我们通过进一步扩展我们的代码库并添加一个coolFunc()将函数作为其唯一参数的函数来看看这一点:
package mainimport ("fmt""time")func myFunc() {fmt.Println("Hello World")time.Sleep(1 * time.Second)}// coolFunc takes in a function// as a parameterfunc coolFunc(a func()) {// it then immediately calls that functinoa()}func main() {fmt.Printf("Type: %Tn", myFunc)// here we call our coolFunc function// passing in myFunccoolFunc(myFunc)}当我们尝试运行它时 , 我们应该看到我们的新输出具有我们 Hello World期望的字符串:
$ go run test.goType: func()Hello World现在 , 这可能会让您感到有些奇怪 。你为什么想做这样的事情?它本质上为您的调用增加了一层抽象 , myFunc并使代码复杂化 , 而没有真正增加太多价值 。
一个简单的装饰器让我们看看如何使用这种模式为我们的代码库添加一些价值 。如果需要 , 我们可以在执行特定函数时添加一些额外的日志记录 , 以突出显示它的开始和结束时间 。
package mainimport ("fmt""time")func myFunc() {fmt.Println("Hello World")time.Sleep(1 * time.Second)}func coolFunc(a func()) {fmt.Printf("Starting function execution: %sn", time.Now())a()fmt.Printf("End of function execution: %sn", time.Now())}func main() {fmt.Printf("Type: %Tn", myFunc)coolFunc(myFunc)}调用此命令后 , 您应该会看到如下所示的日志:
$ go run test.goType: func()Starting function execution: 2018-10-21 11:11:25.011873 +0100 BST m=+0.000443306Hello WorldEnd of function execution: 2018-10-21 11:11:26.015176 +0100 BST m=+1.003743698如您所见 , 我们已经能够有效地包装我的原始函数 , 而无需更改它的实现 。我们现在能够清楚地看到该函数何时启动以及何时完成执行 , 并且它向我们强调了该函数只需大约一秒钟即可完成执行 。
现实世界的例子让我们再看几个例子 , 看看我们如何使用装饰器来获得更多的名声和财富 。我们将使用一个非常简单的 http Web 服务器并装饰我们的端点 , 以便我们可以验证传入请求是否具有特定的标头集 。
如果您想了解更多关于在 Go 中编写简单 REST API 的信息 , 那么我建议您在此处查看我的另一篇文章: 在 Go 中创建 REST API
package mainimport ("fmt""log""net/http")func homePage(w http.ResponseWriter, r *http.Request) {fmt.Println("Endpoint Hit: homePage")fmt.Fprintf(w, "Welcome to the HomePage!")}func handleRequests() {http.HandleFunc("/", homePage)log.Fatal(http.ListenAndServe(":8081", nil))}func main() {handleRequests()}如您所见 , 我们的代码中没有什么特别复杂的 。我们设置了一个net/http 路由器 , 服务于单个/端点 。
让我们添加一个非常简单的身份验证装饰器函数 , 它将检查请求Authorized头是否设置为true传入请求 。
package mainimport ("fmt""log""net/http")func isAuthorized(endpoint func(http.ResponseWriter, *http.Request)) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {fmt.Println("Checking to see if Authorized header set...")if val, ok := r.Header["Authorized"]; ok {fmt.Println(val)if val[0] == "true" {fmt.Println("Header is set! We can serve content!")endpoint(w, r)}} else {fmt.Println("Not Authorized!!")fmt.Fprintf(w, "Not Authorized!!")}})}func homePage(w http.ResponseWriter, r *http.Request) {fmt.Println("Endpoint Hit: homePage")fmt.Fprintf(w, "Welcome to the HomePage!")}func handleRequests() {http.Handle("/", isAuthorized(homePage))log.Fatal(http.ListenAndServe(":8081", nil))}func main() {handleRequests()}


推荐阅读