英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论