在php中,可以非常方便的直接通过字符串来调用指定类的指定方法
<?php
class AA {
public function update() {
echo "function update\n";
}
public static function done() {
echo "function done\n";
}
}
$clas = "AA";
$func1 = "update";
$func2 = "done";
$aaObj = new $clas();
$aaObj->$func1(); // function update
$aaObj->$func2(); // function done
这种写法在很多业务场景中非常nice,最近在一个go项目中也遇到了需要这种写法的场景。在go中并不能直接通过字符串来实例化一个对象或者调用指定方法,需要借助reflect来实现类似上面php代码的功能
package main
import (
"fmt"
"reflect"
)
// service 接口要包含我们希望通过字符串调用的所有类的所有方法
type service interface {
Update()
Create()
}
// Service 类是 service 接口的实现,作为所有我们希望通过字符串调用的类的父类
type Service struct {}
func (s Service) Update() {
fmt.Printf("Service#update\n")
}
func (s Service) Create() {
fmt.Printf("Service#create\n")
}
// 记录所有字符串到对应类对象的反射
var actionMp map[string]reflect.Type = map[string]reflect.Type {
"A": reflect.TypeOf(A{}),
"B": reflect.TypeOf(B{}),
}
// 需要通过字符串调用的类,继承Service
type A struct {
Service
}
func (a A) Update() {
fmt.Printf("function A#update\n")
}
// 需要通过字符串调用的类,继承Service
type B struct {
Service
}
func (b B) Create() {
fmt.Printf("function B#create\n")
}
func newByName(name string) interface{} {
t, found := actionMp[name]
if !found {
panic("name not found!")
}
return reflect.New(t).Elem().Interface()
}
func main() {
a := newByName("A").(service)
a.Update() // function A#update
fmt.Printf("%+v\n", a) // {Service:{}}
fmt.Printf("%+v\n", reflect.ValueOf(a)) // {Service:{}}
fmt.Printf("%+v\n", reflect.TypeOf(a)) // main.A
update := reflect.ValueOf(a).MethodByName("Update")
args := []reflect.Value{}
update.Call(args) // function A#update
}
注意到上面代码实现中,service 接口包含了所有我们希望通过字符串调用的类的方法,然后Service类实现类 service 接口,然后我们需要通过字符串调用的类都继承类Service类即间接实现了service接口。这样写是为可以将通过字符串创建的对象都转换为service接口类型,然后再调用字符串指定的方法。如果不能将所有通过字符串创建的对象转换为具备需要调用方法的统一类型,那我们就需要先给该对象转换为对应的类型,然后才能调用该类型的方法
另外,对于上面的代码,也可以将actionMp的value类型由reflect.Type替换为service接口类型,即
package main
import (
"fmt"
"reflect"
)
type service interface {
Update()
Create()
}
type Service struct {}
func (s Service) Update() {
fmt.Printf("Service#update\n")
}
func (s Service) Create() {
fmt.Printf("Service#create\n")
}
var actionMp map[string]service = map[string]service {
"A": A{},
"B": B{},
}
type A struct {
Service
}
func (a A) Update() {
fmt.Printf("function A#update\n")
}
type B struct {
Service
}
func (b B) Create() {
fmt.Printf("function B#create\n")
}
func newByName(name string) service {
t, found := actionMp[name]
if !found {
panic("name not found!")
}
return t
}
func main() {
a := newByName("A")
a.Update() // function A#update
fmt.Printf("%+v\n", a) // {Service:{}}
fmt.Printf("%+v\n", reflect.ValueOf(a)) // {Service:{}}
fmt.Printf("%+v\n", reflect.TypeOf(a)) // main.A
update := reflect.ValueOf(a).MethodByName("Update") // function A#update
args := []reflect.Value{}
update.Call(args)
}