首页 > 其他分享 >[转]go语言函数装饰器,接口类型变量反射赋值

[转]go语言函数装饰器,接口类型变量反射赋值

时间:2023-10-13 18:15:10浏览次数:43  
标签:代码 Value reflect 接口类型 复制 func go fn 赋值

 

转:原文:https://juejin.cn/post/7115343063119036453

------------------------

 

函数装饰

做基础组件经常需要用到函数修饰,例如我需要对所有被装饰方法里打印start、end。

已知函数签名的装饰

我们经常用的函数装饰器一般都是知道被装饰的方法的签名,然后返回一个同签名的方法。最简单的例子,kite的中间件,实际上就是对handler方法进行装饰。

  go 复制代码
type EndPoint func(ctx context.Context, req interface{}) (resp interface{}, err error)
  go 复制代码
func Middleware(next endpoint.EndPoint) endpoint.EndPoint {
   return func(ctx context.Context, req interface{}) (resp interface{}, err error) {
      // before do something...
      next(ctx, req)
      // after do something...
   }
}

所以本质上就是 fa(fb(fc(fd(req)))) 的调用形式。

这种函数装饰的基础在于每个装饰方法都是知道被装饰函数的签名的,当func签名改变后,装饰方法则不可用了。

如何解决通用函数的装饰?

装饰器本质上是要生成一个func,而反射包里有一个通用的生成func的方法,golang.org/pkg/reflect…

  go 复制代码
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
  sql 复制代码
MakeFunc returns a new function of the given Type that wraps the function fn. When called, that new function does the following:

- converts its arguments to a slice of Values.
- runs results := fn(args).
- returns the results as a slice of Values, one per formal result.

makefunc的入参是type、func 所以需要解决装饰方法的type,可以利用反射包里的TypeOf方法

  go 复制代码
func TypeOf(i interface{}) Type

要装饰通用函数,所有装饰器的入参必须是interface 那么装饰器的初步设计就是

  go 复制代码
func Decorate(f interface{}) interface{} {
   fn := reflect.ValueOf(f)
   v := reflect.MakeFunc(fn.Type(), logicFunc)
   return v.Interface()
}

下一步就是要解决logicFunc的问题了,根据MakeFunc的说明,v就是一个更高了type的func,v(args)的结果就是logicFunc(args)的执行结果 所以logicFunc的逻辑就是装饰器需要做的事,logicFunc的方法签名已经是固定的

  scss 复制代码
func(args []Value) (results []Value)

所以直接考虑logicFunc的实现, 反射包里有个

  scss 复制代码
func (v Value) Call(in []Value) []Value

可以实现任意函数反射后的调用,所以logicFunc内调用原函数也可以直接使用,类比middleware里的next(ctx, req)。

  go 复制代码
func(in []reflect.Value) []reflect.Value { 
   fn := reflect.ValueOf(f)
   // ...
   ret := fn.Call(in)
   // ...
   return ret
}

所以最终通用的函数装饰器为

  go 复制代码
func Decorate(f interface{}) interface{} {
   fn := reflect.ValueOf(f)
   logicFunc := func(in []reflect.Value) []reflect.Value { 
   	fn := reflect.ValueOf(f)
   	// before do something...
   	ret := fn.Call(in)
   	// after do something...
   	return ret
	}
   v := reflect.MakeFunc(fn.Type(), logicFunc)
   return v.Interface()
}

这个装饰器使用起来还是很别扭的

  go 复制代码
func foo(a, b, c int) (int, error) {
   return a + b + c, nil
}

func main() {
   decorateFoo := Decorate(foo2)
   if fn, ok := decorateFoo.(func(a, b, c int) (int, error)); ok {
      ret, err := fn(1, 2, 3)
      fmt.Println(ret, err)
   }
}

每次用之前需要断言成被装饰的方法签名。

能不能不进行断言呢,因为被修饰的方法本身就是入参,但是为了通用性,入参必须定义为interface,那么考虑出参也在使用前定义好签名。也就是装饰器把返回值当入参由使用方来传入。这也是左耳朵耗子推荐的用法

  go 复制代码
func Decorate(decoPtr, f interface{}) error {
   fn := reflect.ValueOf(f)
   decoratedFunc := reflect.ValueOf(decoPtr).Elem()
   logicFunc := func(in []reflect.Value) []reflect.Value { 
   	// before do something...
   	ret := fn.Call(in)
   	// after do something...
   	return ret
	}
   v := reflect.MakeFunc(fn.Type(), logicFunc)
   decoratedFunc.Set(v)
   return nil
}

使用示例

  go 复制代码
func foo1(a, b, c int) (int, error) {
   return a + b + c, nil
}

func main() {
   decorateFoo := foo1
   Decorate(&decorateFoo, foo1)
   ret, err := decorateFoo(1, 2, 3)
   fmt.Println(ret, err)
}

反射更改接口类型的值

有这个想法是因为在通用函数的装饰里,假设所有方法的返回值最后一位都是error,但每个方法签名不一样,怎么能做到在通用装饰里,把所有的返回error置为nil

先看普通变量赋值场景

reflect包里有set**接口,其中通用set接口说明为

  vbnet 复制代码
func (v Value) Set(x Value)

Set assigns x to the value v. It panics if CanSet returns false. As in Go, x's value must be assignable to v's type.

那么对普通变量进行赋值

  css 复制代码
var a int64
v := reflect.ValueOf(a)
v.Set(reflect.ValueOf(int64(3)))
println(a)

这段代码是会panic的,可以看set接口的的说明,查看canset为false

  scss 复制代码
println(v.CanSet())

因为go都是值复制的,所以v内的a只是个副本,不能set就好理解了。稍微变通一下就可以了,利用地址传递。 直接来个普通变量正常赋值的情况

  css 复制代码
var a int64
v := reflect.ValueOf(&a).Elem()
println(v.CanSet())
v.Set(reflect.ValueOf(int64(3)))
println(a)

对接口类型反射赋值

我们先定义一个接口和实现类

  go 复制代码
type itf interface {
   String() string
}

type impl struct {
}

func (*impl) String() string {
   return "itf impl"
}

参考普通变量赋值,可以写接口的赋值方法

  css 复制代码
func main() {
   var a itf
   v := reflect.ValueOf(&a).Elem()
   println(v.CanSet())

   actual := &impl{}
   v.Set(reflect.ValueOf(actual))
   println(a.String())
}

对接口类型反射置nil

有的场景下,是接口类型有值,但是要把这个值置空,例如感知到下游的err后要返回nil,但是每个方法的签名不一样,只知道返回值最后一位是err

  css 复制代码
var a itf
a = &impl{}
v := reflect.ValueOf(&a).Elem()
println(v.CanSet())

v.Set(reflect.ValueOf(nil))
println(a)

上述代码会直接保存,因为对nil进行反射取value得到的是空结果,set不进去。 分析我们需要set的其实是个接口itf的0值,reflect包里是有0值构造器的

  go 复制代码
func Zero(typ Type) Value

Zero方法的入参typ 应该是我们需要操作的接口类型itf, 很自然会想到

  css 复制代码
var b itf
zero := reflect.Zero(reflect.TypeOf(b))
v.Set(zero)

但是这里的b实际上也是nil,所以reflect.Typeof(nil)并不会返回itf的type。所以需要转一道, 取b的地址,然后利用elem取值

  css 复制代码
var b itf
zero := reflect.Zero(reflect.TypeOf(&b).Elem())
v.Set(zero)

所以把接口类型变量置nil的完整写法应该是

  css 复制代码
func main() {
   var a itf
   a = &impl{}
   v := reflect.ValueOf(&a).Elem()
   println(v.CanSet())

   var b itf
   zero := reflect.Zero(reflect.TypeOf(&b).Elem())
   v.Set(zero)
   println(a)
   println(a == nil)
}

分析发现 var b itf实际上是多余的,只是为了一个类型,可以省略掉

  css 复制代码
zero := reflect.Zero(reflect.TypeOf((*itf)(nil)).Elem())

综上完成了对接口类型置nil的操作。


作者:会飞的星星
链接:https://juejin.cn/post/7115343063119036453
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

标签:代码,Value,reflect,接口类型,复制,func,go,fn,赋值
From: https://www.cnblogs.com/oxspirt/p/17762784.html

相关文章

  • RunnerGo测试平台,无代码玩转UI自动化测试
    首先需要进入官网,RunnerGo支持开源,可以自行下载安装,也可以点击右上角体验企业版按钮快速体验 点击体验企业版进入工作台后可以点击页面上方的UI自动化进入到测试页面 创建元素我们可以在元素管理中创建我们测试时需要的元素 这里我们以一个打开百度搜索的场景,添加了......
  • AI 视觉的应用|ZegoAvatar ⾯部表情随动技术解析
    ​ 一、AI“卷”进实时互动2021年,元宇宙概念席卷全球,国内各大厂加速赛道布局,通过元宇宙为不同的应用场景的相关内容生态进行赋能。针对“身份”、“沉浸感”、“低延迟”、“随时随地”这四个元宇宙核心基础,ZEGO即构科技基于互动智能的业务逻辑,提出并落地了ZegoAvatar解决方......
  • Hadoop-Operation category READ is not supported in state standby 故障解决
    在查询hdfs时或者执行程序向hdfs写入数据时遇到报错:OperationcategoryREADisnotsupportedinstatestandby 意思是:该主机状态为待机,不支持操作类别READ.你会发现最基本的hdfs命令都不能执行,例如:hadoopfs-ls/自定义的是nn1还是namenode1等自定义名可以在配置文件h......
  • 一图看懂CodeArts Governance 三大特性,带你玩转开源治理服务
    华为云开源治理服务CodeArtsGovernance是针对软件研发提供的一站式开源软件治理服务,凝聚华为在开源治理上的优秀实践经验,提供开源软件元数据及软件成分分析、恶意代码检测等能力,从合法合规、网络安全、供应安全等维度消减开源软件使用风险,助力企业更加安全、高效地使用开源软件。......
  • MongoDB 4.2 需关闭FlowControl流控机制
    MongoDB4.2版本引入了流量控制特性,用于保持副本集多数提交延迟不超过指定的最大值,从而确保数据的一致性和可靠性。如果复制延迟达到"flowControlTargetLagSeconds":10(秒),流量控制机制就会开始限制主节点上的写入操作。db.adminCommand({getParameter:1,"flowControlTargetL......
  • django常用模型查询
    classStockFilter(django_filters.rest_framework.FilterSet):#搜索名称要与前端搜索名称一致tradeName=django_filters.CharFilter(field_name='trade_name',lookup_expr='icontains')tradeCode=django_filters.CharFilter(field_name='trad......
  • Go语言中的数学计算
    数学常量math.E //自然对数的底,2.718281828459045math.Pi //圆周率,3.141592653589793math.Phi //黄金分割,长/短,1.618033988749895math.MaxInt //9223372036854775807uint64(math.MaxUint) //得先把MaxUint转成uint64才能输出,18446744073709551615math.MaxFloat64 //1.797693......
  • Go语言中的性能测试
    单元测试测试单元的结果是否符合预期//go_test.goimport( "fmt" "testing")funcAdd()int{ fmt.Println("AAAAAAAAAAAAAA") return5}funcSub()int{ fmt.Println("SSSSSSSSSSSSSSSS") return5}funcTestFun1(t*testing.T){......
  • DevEco Hvigor高效编译,构建过程新秘籍
     作者:Lewei,华为终端BG编译构建技术专家DevEco Hvigor是使用TypeScript语言开发的全新轻量化的任务调度工具,针对HarmonyOS应用提供了一系列编译构建任务,支持将HarmonyOS应用编译构建出对应的产物包。作为一款HarmonyOS应用编译构建任务流工具,DevEco Hvigor具备许多可以提升......
  • Django中关于路由匹配的源码分析
    1:关于路由#django中,路由的写法有很多,从最早一点几版本的url(xxxxx)的形式到后面re_path(xxxx),以及参考flask的path(xxxx)的格式。#无论是哪种,实现的功能本质上就是,匹配url和对应的额视图函数,换言之,就是,找到用户访问的url对应的视图函数,并且执行它。#下面是urls.p......