通过接口访问嵌入类型字段

huangapple go评论93阅读模式
英文:

Accessing embedded type fields through interface

问题

似乎我错过了一些重要的东西,但我无法弄清楚是什么。我使用反射通过接口访问嵌入类型字段的问题是,根据runtime/pprof的说法,它会消耗大量的CPU。我不想在所有车辆上实现Setter和Getter方法,所以有没有更好的方法来解决这个问题?

简化的示例:

package main

import (
	"reflect"
	"fmt"
)

// "contract"是所有车辆都有一个嵌入的引擎
type Vehicle interface {}

type Engine struct {
	Power float64
	Cubic float64
}

type Car struct {
	Engine
	Weight   float64
	TopSpeed float64
}

// 这里还有更多带有引擎的车辆...

func EngineCheck(v Vehicle) {
	// 这样是不起作用的:
	// power := v.Power
	// 反射可以工作,但会消耗大量的CPU:
	power := reflect.ValueOf(v).Elem().FieldByName("Power").Interface().(float64)
	fmt.Println(power)
}

func main() {
	c1 := &Car{Engine{120.0, 1.2}, 1.5, 250}

	EngineCheck(c1)
}
英文:

It seems like I missed something important but I can not figure out what it is. I use reflect to access embedded type fields through an interface. The problem I have is that according to runtime/pprof it eats up a lot of CPU. I do not like to implement Setter and Getter methods on all Vehicles so is there a better way of doing this?

Simplified sample:

package main

import(
	"reflect"
	"fmt"
)

// the "contract" is that all vehicles have an embedded Engine
type Vehicle interface {}

type Engine struct {
	Power float64
	Cubic float64
}

type Car struct {
	Engine
	Weight float64
	TopSpeed float64
}

// more Vehicles with Engines here...

func EngineCheck(v Vehicle) {
	// this does not work:
	//power := v.Power
	// reflection works but eats up a lot of CPU:
	power := reflect.ValueOf(v).Elem().FieldByName("Power").Interface().(float64)
	fmt.Println(power)
}

func main() {
	c1 := &Car{Engine{120.0, 1.2}, 1.5, 250}

	EngineCheck(c1)
}

答案1

得分: 2

如果你知道确切的类型,你可以使用类型断言(type assertion),这样速度更快,只有在失败时才会使用反射(reflection)。

例如:

func EngineCheck(v Vehicle) {
    var power float64
    if eng, ok := v.(*Car); ok {
        power = eng.Power
    } else {
        power = reflect.ValueOf(v).Elem().FieldByName("Power").Interface().(float64)
    }
    fmt.Println(power)
}

请注意,类型Car*Car是不同的,上面的示例只有在传递的值确实是指针*Car时才会“跳过”反射部分。

如果有多个可能的“可接受”类型,你可以使用类型开关(type switch)。例如,如果传递了Car*Car,你可以从两者中获取Power值。同样,如果传递了Engine*Engine,也是同样的情况。

func EngineCheck(v Vehicle) {
    var power float64
    switch i := v.(type) {
    case *Car:
        power = i.Power
    case Car:
        power = i.Power
    case *Engine:
        power = i.Power
    case Engine:
        power = i.Power
    default:
        power = reflect.ValueOf(v).Elem().FieldByName("Power").Interface().(float64)
    }
    fmt.Println(power)
}

但是,惯用的解决方案仍然是在Vehicle中添加一个获取器函数(getter function):

type Vehicle interface {
    GetPower() float64
}

请注意,你不必在每个地方都实现GetPower()。如果你在Engine中实现它:

func (e Engine) GetPower() float64 {
    return e.Power
}

并将Engine嵌入到Car中(就像你所做的那样),你的Car类型将自动在其方法集中拥有此GetPower()方法(被提升),因此它将自动实现Vehicle接口。然后,你的EngineCheck()函数将变得非常简单:

func EngineCheck(v Vehicle) {
    fmt.Println(v.GetPower())
}

Go Playground上尝试这三种变体。

英文:

You could use type assertion if you know the exact type which is fast, and only revert to reflection if that fails.

For example:

func EngineCheck(v Vehicle) {
	var power float64
	if eng, ok := v.(*Car); ok {
		power = eng.Power
    } else {
		power = reflect.ValueOf(v).Elem().FieldByName("Power").Interface().(float64)
    }
	fmt.Println(power)
}

Note that the types Car and *Car are different, and the above example would only "skip" the reflection part if the value you pass is indeed a pointer: *Car.

If there are multiple possible "acceptable" types, you could use a type switch. For example if you pass a Car or *Car, you can get the Power value from both. Also if Engine or *Engine would be passed, the same thing applies.

func EngineCheck(v Vehicle) {
	var power float64
    switch i := v.(type) {
	case *Car:
    	power = i.Power
	case Car:
    	power = i.Power
	case *Engine:
    	power = i.Power
	case Engine:
		power = i.Power
    default:
		power = reflect.ValueOf(v).Elem().FieldByName("Power").Interface().(float64)
    }
	fmt.Println(power)
}

But the idiomatic solution would still be to add a getter function to Vehicle:

type Vehicle interface {
    GetPower() float64
}

Note that you do not have to implement GetPower() everywhere. If you implement it at the Engine:

func (e Engine) GetPower() float64 {
    return e.Power
}

And you embed Engine into Car (as you did), your Car type will automatically have this GetPower() method (promoted) in its method set and thus it will automatically implement Vehicle. And then your EngineCheck() function would be as simple as:

func EngineCheck(v Vehicle) {
    fmt.Println(v.GetPower())
}

Try all these 3 variants on the Go Playground.

huangapple
  • 本文由 发表于 2016年2月15日 15:35:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/35403796.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定