言語関数デコレータ、インターフェイス型変数リフレクションの割り当てに移動します

機能装飾

基本的なコンポーネントを作成するには、関数装飾を使用する必要があることがよくあります。たとえば、装飾されたすべてのメソッドの開始と終了を印刷する必要があります。

既知の関数シグネチャの装飾

私たちがよく使用する関数デコレータは、一般に、装飾されているメソッドのシグネチャを知っており、同じシグネチャを持つメソッドを返します。最も単純な例であるのミドルウェアは、実際にはハンドラーメソッドを装飾します。

type EndPoint func(ctx context.Context, req interface{}) (resp interface{}, err error)
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を生成する一般的な方法があります。golang.org/ pkg/reflect…

func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
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であるため、装飾メソッドのタイプを解決する必要があります。リフレクションパッケージでTypeOfメソッドを使用できます。

func TypeOf(i interface{}) Type

ジェネリック関数をデコレートするには、すべてのデコレータの入力パラメータがインターフェイスである必要があります。その後、デコレータの予備設計は次のようになります。

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

次のステップは、logicFuncの問題を解決することです。MakeFuncの説明によると、vはより高い型の関数であり、v(args)の結果はlogicFunc(args)の実行結果であるため、logicFuncのロジックはデコレータが行う必要があることです。logicFuncのメソッドシグネチャが修正されました。

func(args []Value) (results []Value)

したがって、logicFuncの実装を直接検討してください。リフレクションパッケージがあります

func (v Value) Call(in []Value) []Value

リフレクション後の任意の関数の呼び出しを実現できるため、ミドルウェアのnext(ctx、req)と同様に、元の関数をlogicFuncで直接使用することもできます。

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

したがって、最終的なジェネリック関数デコレータは

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()
}

このデコレータはまだ使いにくいです

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,那么考虑出参也在使用前定义好签名。也就是装饰器把返回值当入参由使用方来传入。这也是左耳朵耗子推荐的用法

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
}

使用示例

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接口说明为

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.

那么对普通变量进行赋值

var a int64
v := reflect.ValueOf(a)
v.Set(reflect.ValueOf(int64(3)))
println(a)

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

println(v.CanSet())

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

var a int64
v := reflect.ValueOf(&a).Elem()
println(v.CanSet())
v.Set(reflect.ValueOf(int64(3)))
println(a)

对接口类型反射赋值

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

type itf interface {
   String() string
}

type impl struct {
}

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

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

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

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值构造器的

func Zero(typ Type) Value

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

var b itf
zero := reflect.Zero(reflect.TypeOf(b))
v.Set(zero)

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

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

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

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实际上是多余的,只是为了一个类型,可以省略掉

zero := reflect.Zero(reflect.TypeOf((*itf)(nil)).Elem())

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

おすすめ

転載: juejin.im/post/7115343063119036453