装饰器在Python和TypeScript等其他编程语言中肯定更为突出,但这并不是说你不能在Go中使用它们。事实上,对于某些问题,使用装饰器是我们希望在本教程中找到的完美解决方案。了解装饰器模式 装饰器本质上允许您包装现有功能并在顶部附加或添加您自己的自定义功能。 在Go中,函数被视为第一类对象,这本质上意味着您可以像传递变量一样传递它们。让我们用一个非常简单的例子来看看这个:packagemainimport(fmttime)funcmyFunc(){fmt。Println(HelloWorld)time。Sleep(1time。Second)}funcmain(){fmt。Printf(Type:T,myFunc)} 所以,在这个例子中,我们定义了一个名为的函数myFunc,它简单地打印出HelloWorld。然而,在我们的main()函数体中,我们已经调用fmt。Printf并且我们习惯于T打印出我们作为第二个参数传入的值的类型。在这种情况下,我们正在传递myFunc,随后将打印出以下内容:goruntest。goType:func() 那么,这对我们Go开发人员意味着什么?好吧,它强调了函数可以在我们代码库的其他部分中传递并用作参数的事实。 让我们通过进一步扩展我们的代码库并添加一个coolFunc()将函数作为其唯一参数的函数来看看这一点:packagemainimport(fmttime)funcmyFunc(){fmt。Println(HelloWorld)time。Sleep(1time。Second)}coolFunctakesinafunctionasaparameterfunccoolFunc(afunc()){itthenimmediatelycallsthatfunctinoa()}funcmain(){fmt。Printf(Type:T,myFunc)herewecallourcoolFuncfunctionpassinginmyFunccoolFunc(myFunc)} 当我们尝试运行它时,我们应该看到我们的新输出具有我们HelloWorld期望的字符串:goruntest。goType:func()HelloWorld 现在,这可能会让您感到有些奇怪。你为什么想做这样的事情?它本质上为您的调用增加了一层抽象,myFunc并使代码复杂化,而没有真正增加太多价值。一个简单的装饰器 让我们看看如何使用这种模式为我们的代码库添加一些价值。如果需要,我们可以在执行特定函数时添加一些额外的日志记录,以突出显示它的开始和结束时间。packagemainimport(fmttime)funcmyFunc(){fmt。Println(HelloWorld)time。Sleep(1time。Second)}funccoolFunc(afunc()){fmt。Printf(Startingfunctionexecution:s,time。Now())a()fmt。Printf(Endoffunctionexecution:s,time。Now())}funcmain(){fmt。Printf(Type:T,myFunc)coolFunc(myFunc)} 调用此命令后,您应该会看到如下所示的日志:goruntest。goType:func()Startingfunctionexecution:2018102111:11:25。0118730100BSTm0。000443306HelloWorldEndoffunctionexecution:2018102111:11:26。0151760100BSTm1。003743698 如您所见,我们已经能够有效地包装我的原始函数,而无需更改它的实现。我们现在能够清楚地看到该函数何时启动以及何时完成执行,并且它向我们强调了该函数只需大约一秒钟即可完成执行。现实世界的例子 让我们再看几个例子,看看我们如何使用装饰器来获得更多的名声和财富。我们将使用一个非常简单的httpWeb服务器并装饰我们的端点,以便我们可以验证传入请求是否具有特定的标头集。 如果您想了解更多关于在Go中编写简单RESTAPI的信息,那么我建议您在此处查看我的另一篇文章:在Go中创建RESTAPIpackagemainimport(fmtlognethttp)funchomePage(whttp。ResponseWriter,rhttp。Request){fmt。Println(EndpointHit:homePage)fmt。Fprintf(w,WelcometotheHomePage!)}funchandleRequests(){http。HandleFunc(,homePage)log。Fatal(http。ListenAndServe(:8081,nil))}funcmain(){handleRequests()} 如您所见,我们的代码中没有什么特别复杂的。我们设置了一个nethttp路由器,服务于单个端点。 让我们添加一个非常简单的身份验证装饰器函数,它将检查请求Authorized头是否设置为true传入请求。packagemainimport(fmtlognethttp)funcisAuthorized(endpointfunc(http。ResponseWriter,http。Request))http。Handler{returnhttp。HandlerFunc(func(whttp。ResponseWriter,rhttp。Request){fmt。Println(CheckingtoseeifAuthorizedheaderset。。。)ifval,ok:r。Header〔Authorized〕;ok{fmt。Println(val)ifval〔0〕true{fmt。Println(Headerisset!Wecanservecontent!)endpoint(w,r)}}else{fmt。Println(NotAuthorized!!)fmt。Fprintf(w,NotAuthorized!!)}})}funchomePage(whttp。ResponseWriter,rhttp。Request){fmt。Println(EndpointHit:homePage)fmt。Fprintf(w,WelcometotheHomePage!)}funchandleRequests(){http。Handle(,isAuthorized(homePage))log。Fatal(http。ListenAndServe(:8081,nil))}funcmain(){handleRequests()} 注意:这绝对不是保护RESTAPI的正确方法,我建议您考虑使用JWT或OAuth2来实现该目标! 所以,让我们分解一下并尝试了解发生了什么! 我们创建了一个名为的新装饰器函数isAuthorized(),它接收一个与原始homePage函数具有相同签名的函数。然后返回一个http。Handler。 在我们的isAuthorized()函数体中,我们返回一个newhttp。HandlerFunc来验证我们的Authorizedheader的工作是set和equalstrue。现在,这是一个大大简化的版本OAuth2身份验证授权,有一些细微的差异,但它让您大致了解它是如何工作的。 然而,要注意的关键是我们已经设法装饰现有端点并在所述端点周围添加某种形式的身份验证,而无需更改该功能的现有实现。 现在,如果我们要添加一个我们想要保护的新端点,我们可以轻松地这样做:defineournewEndpointfunction。Noticehow,yetagain,wedontdoanyauthenticationbasedstuffinthebodyofthisfunctionfuncnewEndpoint(whttp。ResponseWriter,rhttp。Request){fmt。Println(MyNewEndpoint)fmt。Fprintf(w,Mysecondendpoint)}funchandleRequests(){http。Handle(,isAuthorized(homePage))registerournewendpointanddecorateourfunctionwithourisAuthorizedDecoratorhttp。Handle(new,isAuthorized(newEndpoint))log。Fatal(http。ListenAndServe(:8081,nil))} 这突出了装饰器模式的主要优点,在我们的代码库中包装代码非常简单。我们可以使用相同的方法轻松添加新的经过身份验证的端点结论 希望本教程有助于揭开装饰器的神秘面纱,以及如何在自己的基于Go的程序中使用装饰器模式。我们了解了装饰器模式的好处以及如何使用它来用新功能包装现有功能。 在本教程的第二部分,我们查看了一个更现实的示例,说明如何在自己的生产级Go系统中使用它。 如果您喜欢本教程,请随时广泛分享这篇文章,它确实对网站有帮助,我将不胜感激!如果您有任何问题和或意见,请在下面的评论部分告诉我!