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