当前位置: 首页 > 科技观察

如何在编程模式下实现装饰器

时间:2023-03-17 17:28:11 科技观察

Go转载请联系Golang梦工厂公众号。前言大家好,我是asong。今天想和大家聊一聊如何用Go实现装饰器代码。你为什么会有这个想法?最近由于需要看项目的python代码,在本项目中应用了大量的装饰器代码。装饰器代码可以在整个文本中共享,从而减少冗余代码。Python的语法糖可以很容易的实现装饰器,但是Go语言没有太多的糖,而且是没有虚拟机的强类型静态语言,所以没有办法像Java和Python一样写出优雅的装饰器的代码装饰器,但它也是可以实现的。今天我们就来看看如何用Go语言写装饰器代码吧!什么是装饰器?生活水平提高了,基本上人人都有手机了。大家都知道,手机屏幕掉在地上很容易碎。如何避免这个问题,在不破坏手机屏幕结构的情况下,如何让我们的手机更耐损坏呢?其实我们只需要花几块钱买一块钢化膜,钢化膜不会改变手机原有屏幕的结构,让手机变得更耐摔。根据上面的例子,可以引入本文的核心->装饰器。装饰器的本质是:函数装饰器用于在源代码中对函数进行“标记”,以某种方式增强函数的行为。装饰器是一个强大的特性,但是如果你想掌握它们,你必须了解闭包!闭包的概念将在下一节中解释。下面看看python是如何使用装饰器的:defmetric(fn):@functools.wraps(fn)deftimer(*arag,**kw):start=time.time()num=fn(*arag,**kw)end=time.time()times=(end-start)*1000print('%sexecutedin%sms'%(fn.__name__,times))returnnumreturntimer@metricdefSum(x,y):time.sleep(0.0012)returnx+y;Sum(10,20)这里实现函数很简单,metric是一个装饰器函数,可以作用于任意函数,打印函数的执行时间。有了这个装饰器,我们就更容易知道任何函数的执行时间。装饰器使用场景简单总结:插入日志:让面向切面的编程更简单。缓存:读写缓存使用装饰器实现,减少冗余代码。事务处理:使代码看起来更简洁。权限检查:权限检查器是一组代码,减少了冗余代码。装饰器的使用场景还有很多,就不一一列举了。下面就来看看如何使用Go来实现装饰器代码吧!闭包装饰器的实现和闭包密不可分,所以我们先来了解一下什么是闭包!我们经常混淆闭包和匿名函数是有原因的:在开始使用匿名函数之前,在函数内部定义函数并不常见。此外,闭包问题仅在涉及嵌套函数时出现。因此,很多人同时知道这两个概念。实际上,闭包是指扩展范围的函数,包括函数定义体中引用但定义体中未定义的非全局变量。函数是否匿名并不重要,关键是它可以访问定义在定义体之外的非全局变量。光看概念其实很难理解闭包。让我们通过例子来理解它。funcmakeAverager()func(valfloat32)float32{series:=make([]float32,0)returnfunc(valfloat32)float32{series=append(series,val)total:=float32(0)for_,v:=rangeseries{total+=v}returntotal/float32(len(series))}}funcmain(){avg:=makeAverager()fmt.Println(avg(10))fmt.Println(avg(30))}这个例子,你猜结果是什么?10,30还是10,20?运行一下,答案出来了:10,20。为什么会这样?我们来分析一下!上面代码中makeAverager的写法在C语言中是不允许的,因为在C语言中,函数中的内存分配是在栈上的。makeAverager返回后,这部分栈被回收了,但是在Go语言中是没有问题的,因为Go语言会进行escapeanalyze来分析变量的作用域,并在堆上分配变量的内存。我们使用gobuild--gcflags=-m。/test/test1.go看分析结果:#command-line-argumentstest/test1.go:21:13:inliningcalltofmt.Printlntest/test1.go:22:13:inliningcalltofmt.Printlntest/test1.go:8:2:movedtoheap:seriestest/test1.go:8:16:make([]float32,0)escapestoheaptest/test1.go:9:9:funcliteralestoheaptest/test1.go:21:17:avg(10)escapestoheaptest/test1.go:21:13:[]interface{}literaldoesnotescapetest/test1.go:22:17:avg(30)escapestoheaptest/test1.go:22:13:[]interface{}literaldoesnotescape:1:.thisdoesnotescape从运行结果,我们可以看到series、func和avg都逃到了堆中。所以我们可以得出结论,series变量和func(valfloat32)float32{}被引用后,他所在的函数就结束了,不会马上销毁,也变相的延长了函数的生命周期!总结:总而言之,闭包是一个函数,它保留定义函数时存在的自由变量的绑定,以便在调用函数时,即使定义范围不再可用,这些绑定仍然可用。请注意,只有嵌套在其他函数中的函数可能需要处理不在全局范围内的外部变量。Gin中装饰器的应用大家应该都用过Gin作为web框架。提供在注册路由时使用中间件,可以拦截http请求-响应生命周期中的特殊功能。请求-响应生命周期中可以注册多个函数。每个中间件执行不同的功能。一个中间件执行完后,就轮到下一个中??间件执行了。这个中间件其实就是使用的装饰器,我们看一个简单的例子:"{c.JSON(200,gin.H{"code":1000,"msg":"Notloggedin",})return}}}funcmain(){r:=gin.Default()group:=r.Group("/api/asong",VerifyHeader()){group.GET("/ping",func(context*gin.Context){context.JSON(200,gin.H{"message":"pong",})})}r.Run()}这段代码很简单,我们只需要写一个VerifyHeader函数,在注册路由的时候加上即可。当有请求进来的时候,gin会先执行。HanderFunc函数在Gin框架中是存储在一个slice中的,所以在添加中间件的时候要注意添加的顺序!//HandlerFunc定义了gin中间件使用的handler作为返回值handlers)ifinalSize>=int(abortIndex){panic("toomanyhandlers")}mergedHandlers:=make(HandlersChain,finalSize)copy(mergedHandlers,group.Handlers)copy(mergedHandlers[len(group.Handlers):],handlers)returnmergedHandlers}net/http使用装饰器上面我们看到了装饰器在Gin框架中的应用,这种设计大大减少了冗余代码的出现,提高了代码的可扩展性。然后我们将在标准库http包上实现一个装饰器并实践一下。我们知道Go语言的http标准库是不能使用中间件的,那么我们的机会来了,我们来为他实现一个吧!看代码:typeDecoratorHandlerfunc(http.HandlerFunc)http.HandlerFuncfuncMiddlewareHandlerFunc(hphttp.HandlerFunc,decors...DecoratorHandler)http.HandlerFunc{ford:=rangedecors{dp:=decors[len(decors)-1-d]hp=dp(hp)}returnhp}funcVerifyHeader(hhttp.HandlerFunc)http.HandlerFunc{returnfunc(whttp.ResponseWriter,r*http.Request){token:=r.Header.Get("token")iftoken==""{fmt.Fprintf(w,r.URL.Path+"response:NotLoggedin")return}h(w,r)}}funcPong(whttp.ResponseWriter,r*http.Request){fmt.Fprintf(w,r.URL.Path+"response:pong")return}funcmain(){http.HandleFunc("/api/asong/ping",MiddlewareHandlerFunc(Pong,VerifyHeader))err:=http.ListenAndServe(":8080",nil)iferr!=nil{log.Fatal("ListenAndServe:",err)}}实现起来比较简单,这里我们重新声明DecoratorHandler类型,本质上就是func(http.HandlerFunc)http.HandlerFunc,方便我们添加中间件函数,中间件按照相加的顺序执行。综上所述,本文到此结束。在本文中,我们了解了闭包的概念。通过闭包,我们学会了如何在Go语言中使用装饰器。因为Go语言不支持注解这种语法糖,所以我们使用装饰器还是有点难看,但是这个思路还是很重要的。我们可以在日常开发中参考这个思想,写出更好的代码!