装饰器在 Python 和 TypeScript 等其他编程语言中肯定更为突出,但这并不是说你不能在 Go 中使用它们。事实上,对于某些问题,使用装饰器是我们希望在本教程中找到的完美解决方案。
装饰器本质上允许您包装现有功能并在顶部附加或添加您自己的自定义功能。
在 Go 中,函数被视为第一类对象,这本质上意味着您可以像传递变量一样传递它们。让我们用一个非常简单的例子来看看这个:
package main
import (
"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.go
Type: func()
那么,这对我们 Go 开发人员意味着什么?好吧,它强调了函数可以在我们代码库的其他部分中传递并用作参数的事实。
让我们通过进一步扩展我们的代码库并添加一个coolFunc()将函数作为其唯一参数的函数来看看这一点:
package main
import (
"fmt"
"time"
)
func myFunc() {
fmt.Println("Hello World")
time.Sleep(1 * time.Second)
}
// coolFunc takes in a function
// as a parameter
func coolFunc(a func()) {
// it then immediately calls that functino
a()
}
func main() {
fmt.Printf("Type: %Tn", myFunc)
// here we call our coolFunc function
// passing in myFunc
coolFunc(myFunc)
}
当我们尝试运行它时,我们应该看到我们的新输出具有我们 Hello World期望的字符串:
$ go run test.go
Type: func()
Hello World
现在,这可能会让您感到有些奇怪。你为什么想做这样的事情?它本质上为您的调用增加了一层抽象,myFunc并使代码复杂化,而没有真正增加太多价值。
让我们看看如何使用这种模式为我们的代码库添加一些价值。如果需要,我们可以在执行特定函数时添加一些额外的日志记录,以突出显示它的开始和结束时间。
package main
import (
"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.go
Type: func()
Starting function execution: 2018-10-21 11:11:25.011873 +0100 BST m=+0.000443306
Hello World
End of function execution: 2018-10-21 11:11:26.015176 +0100 BST m=+1.003743698
如您所见,我们已经能够有效地包装我的原始函数,而无需更改它的实现。我们现在能够清楚地看到该函数何时启动以及何时完成执行,并且它向我们强调了该函数只需大约一秒钟即可完成执行。
让我们再看几个例子,看看我们如何使用装饰器来获得更多的名声和财富。我们将使用一个非常简单的 http Web 服务器并装饰我们的端点,以便我们可以验证传入请求是否具有特定的标头集。
如果您想了解更多关于在 Go 中编写简单 REST API 的信息,那么我建议您在此处查看我的另一篇文章: 在 Go 中创建 REST API
package main
import (
"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 main
import (
"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()
}
注意:这绝对不是保护 REST API 的正确方法,我建议您考虑使用 JWT 或 OAuth2 来实现该目标!
所以,让我们分解一下并尝试了解发生了什么!
我们创建了一个名为的新装饰器函数isAuthorized(),它接收一个与原始homePage函数具有相同签名的函数。然后返回一个http.Handler.
在我们的isAuthorized()函数体中,我们返回一个 new http.HandlerFunc来验证我们的Authorized header的工作是 set 和 equals true。现在,这是一个大大简化的版本OAuth2身份验证/授权,有一些细微的差异,但它让您大致了解它是如何工作的。
然而,要注意的关键是我们已经设法装饰现有端点并在所述端点周围添加某种形式的身份验证,而无需更改该功能的现有实现。
现在,如果我们要添加一个我们想要保护的新端点,我们可以轻松地这样做:
// define our newEndpoint function. Notice how, yet again,
// we don't do any authentication based stuff in the body
// of this function
func newEndpoint(w http.ResponseWriter, r *http.Request) {
fmt.Println("My New Endpoint")
fmt.Fprintf(w, "My second endpoint")
}
func handleRequests() {
http.Handle("/", isAuthorized(homePage))
// register our /new endpoint and decorate our
// function with our isAuthorized Decorator
http.Handle("/new", isAuthorized(newEndpoint))
log.Fatal(http.ListenAndServe(":8081", nil))
}
这突出了装饰器模式的主要优点,在我们的代码库中包装代码非常简单。我们可以使用相同的方法轻松添加新的经过身份验证的端点
希望本教程有助于揭开装饰器的神秘面纱,以及如何在自己的基于 Go 的程序中使用装饰器模式。我们了解了装饰器模式的好处以及如何使用它来用新功能包装现有功能。
在本教程的第二部分,我们查看了一个更现实的示例,说明如何在自己的生产级 Go 系统中使用它。
如果您喜欢本教程,请随时广泛分享这篇文章,它确实对网站有帮助,我将不胜感激!如果您有任何问题和/或意见,请在下面的评论部分告诉我!