go language function decorator, variable reflektionszuweisung vom schnittstellentyp

Funktionsdekoration

Es ist oft notwendig, Funktionsdekorationen zu verwenden, um grundlegende Komponenten herzustellen, zum Beispiel muss ich Start und Ende für alle dekorierten Methoden drucken.

Dekoration für bekannte Funktionssignaturen

Die häufig verwendeten Funktionsdekorateure kennen im Allgemeinen die Signatur der dekorierten Methode und geben dann eine Methode mit derselben Signatur zurück. Das einfachste Beispiel, die Middleware von kite , schmückt tatsächlich die Handler-Methode.

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...
   }
}

Es ist also im Wesentlichen die aufrufende Form von fa (fb(fc( fd (req)))) .

Die Grundlage dieser Funktionsdekoration ist, dass jede dekorierte Methode die Signatur der dekorierten Funktion kennt. Wenn die func-Signatur geändert wird, ist die dekorierte Methode nicht verfügbar.

Wie löse ich die Dekoration generischer Funktionen?

Der Decorator dient im Wesentlichen dazu, eine Funktion zu generieren, und das Reflection-Paket verfügt über eine allgemeine Methode zum Erstellen von Funktionen, 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.

Die Eingabeparameter von makefunc sind type und func, Sie müssen also den Typ der Dekorationsmethode lösen, Sie können die TypeOf-Methode im Reflection-Paket verwenden

func TypeOf(i interface{}) Type

Um eine generische Funktion zu dekorieren, müssen die Eingabeparameter aller Dekorateure Schnittstelle sein, dann ist das vorläufige Design des Dekorateurs

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

Der nächste Schritt ist die Lösung des Problems von logicFunc: Gemäß der Beschreibung von MakeFunc ist v eine Funktion höheren Typs, und das Ergebnis von v(args) ist das Ausführungsergebnis von logicFunc(args), also die Logik von logicFunc ist, was der Dekorateur tun muss, wurde die Methodensignatur von logicFunc behoben

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

Betrachten Sie also direkt die Implementierung von logicFunc, es gibt ein Reflexionspaket

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

Der Aufruf beliebiger Funktionen nach Reflektion ist realisierbar, somit kann die Originalfunktion auch direkt in logicFunc verwendet werden, analog zu next(ctx, req) in Middleware.

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

Der letzte generische Funktionsdekorateur ist also

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

Dieser Dekorateur ist immer noch umständlich zu bedienen

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

Die Signatur der dekorierten Methode muss vor jeder Verwendung bestätigt werden.

能不能不进行断言呢,因为被修饰的方法本身就是入参,但是为了通用性,入参必须定义为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的操作。

Ich denke du magst

Origin juejin.im/post/7115343063119036453
Empfohlen
Rangfolge