Автор: Джон Боднер | Адрес:Learning to Use Go Reflection
что такое отражение
По большей части использование переменных, типов и функций в Go очень просто.
Когда вам нужен тип, определите его следующим образом:
type Foo struct {
A int
B string
}
Когда вам нужна переменная, определите ее так:
var x Foo
Когда вам нужна функция, определите ее так:
func DoSomething(f Foo) {
fmt.Println(f.A, f.B)
}
Но иногда переменные, которые вы хотите использовать, зависят от информации времени выполнения, которой не существует во время программирования. Скажем, данные поступают из файла или из Интернета, и вы хотите сопоставить их с переменной, и они могут быть разных типов. В таких сценариях необходимо использовать отражение. Отражение позволяет проверять типы, создавать, обновлять, проверять переменные и организовывать структуры во время выполнения.
Отражение в Go вращается вокруг трех концепций: типов, видов и значений. Исходный код для реализации отражения находится в пакете отражения стандартной библиотеки Go.
Тип чека
Во-первых, давайте посмотрим на типы. Вы можете получить тип переменной, вызвав функцию в форме Reflect.TypeOf(var), которая вернет переменную типа Reflect.Type. Методы операций в Reflect.Type включают различные типы информации, которые определяют переменные этот тип.
Первый метод, который мы рассмотрим, это Name(), который возвращает имя типа. Некоторые типы, такие как срезы или указатели, не имеют имени типа и возвращают пустую строку.
Следующим вводным методом является Kind(), который, на мой взгляд, является первым действительно полезным методом. Тип, то есть категория, такая как срез среза, карта карты, указатель указателя, структура структуры, интерфейс интерфейса, строка строки, массив массива, функция функция, целое число int или другие базовые типы. Разницу между типом и видом не так просто понять, но вы можете думать об этом так:
Когда вы определяете структуру с именем Foo, она имеет тип struct и тип Foo.
При использовании отражения мы должны осознавать, что при использовании пакета Reflect предполагается, что вы знаете, что делаете, и если вы используете его неправильно, вы будете паниковать. Например, если вы вызываете метод для типа int, который используется только для типа структуры, ваш код вызовет панику. Мы всегда должны помнить, какие типы имеют какие методы использовать, чтобы избежать паники.
Если переменная является указателем, картой, срезом, каналом или массивом, то тип переменной может вызвать метод varType.Elem().
Если переменная является структурой, то вы можете использовать отражение, чтобы получить количество ее полей, и вы можете получить информацию о каждом поле, содержащемся в структуре Reflect.StructField. Reflect.StructField содержит имя поля, порядок, тип, метку.
Предисловие не так ясно, как строка кода.В следующем примере выводится информация о типах различных переменных.
type Foo struct {
A int `tag1:"First Tag" tag2:"Second Tag"`
B string
}
func main() {
sl := []int{1, 2, 3}
greeting := "hello"
greetingPtr := &greeting
f := Foo{A: 10, B: "Salutations"}
fp := &f
slType := reflect.TypeOf(sl)
gType := reflect.TypeOf(greeting)
grpType := reflect.TypeOf(greetingPtr)
fType := reflect.TypeOf(f)
fpType := reflect.TypeOf(fp)
examiner(slType, 0)
examiner(gType, 0)
examiner(grpType, 0)
examiner(fType, 0)
examiner(fpType, 0)
}
func examiner(t reflect.Type, depth int) {
fmt.Println(strings.Repeat("\t", depth), "Type is", t.Name(), "and kind is", t.Kind())
switch t.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice:
fmt.Println(strings.Repeat("\t", depth+1), "Contained type:")
examiner(t.Elem(), depth+1)
case reflect.Struct:
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Println(strings.Repeat("\t", depth+1), "Field", i+1, "name is", f.Name, "type is", f.Type.Name(), "and kind is", f.Type.Kind())
if f.Tag != "" {
fmt.Println(strings.Repeat("\t", depth+2), "Tag is", f.Tag)
fmt.Println(strings.Repeat("\t", depth+2), "tag1 is", f.Tag.Get("tag1"), "tag2 is", f.Tag.Get("tag2"))
}
}
}
}
Результат выглядит следующим образом:
Type is and kind is slice
Contained type:
Type is int and kind is int
Type is string and kind is string
Type is and kind is ptr
Contained type:
Type is string and kind is string
Type is Foo and kind is struct
Field 1 name is A type is int and kind is int
Tag is tag1:"First Tag" tag2:"Second Tag"
tag1 is First Tag tag2 is Second Tag
Field 2 name is B type is string and kind is string
Type is and kind is ptr
Contained type:
Type is Foo and kind is struct
Field 1 name is A type is int and kind is int
Tag is tag1:"First Tag" tag2:"Second Tag"
tag1 is First Tag tag2 is Second Tag
Field 2 name is B type is string and kind is string
Создать экземпляр
Помимо проверки типа переменных, вы также можете использовать для получения, установки и создания переменных. Сначала создайте экземпляр типа Reflect.Value с помощью refVal := Reflect.ValueOf(var) . Если вы хотите обновить значение путем отражения, вам нужно получить указатель на переменную refPtrVal := Reflect.ValueOf(&var) , если вы этого не сделаете, вы можете только прочитать значение, а не установить значение.
Как только вы получите Reflect.Value переменной, вы можете получить информацию о типе Reflect.Type переменной через свойство Type значения.
Если вы хотите обновить значение, не забудьте передать указатель, и при настройке вы должны сначала разыменовать его и обновить значение через refPtrVal.Elem().Set(newRefVal).Параметр, переданный в Set, также должен быть отражен .Тип значения.
Если вы хотите создать новую переменную, вы можете сделать это через Reflect.New(varType), переданный параметр имеет тип Reflect.Type, метод вернет указатель, как описано ранее, вы можете использовать Elem().Set ( ), чтобы установить его значение.
Наконец, с помощью метода Interface() вы получаете обычную переменную. В Go нет дженериков, тип переменной будет потерян и метод Interface() вернет переменную типа interface{} . Если вы создаете указатель, чтобы иметь возможность обновлять значение, вам нужно использовать Elem().Interface() для получения переменной. Но в любом из приведенных выше случаев вам необходимо преобразовать переменную типа interface{} в фактический тип, чтобы использовать ее.
Ниже приведен код, реализующий эти концепции.
type Foo struct {
A int `tag1:"First Tag" tag2:"Second Tag"`
B string
}
func main() {
greeting := "hello"
f := Foo{A: 10, B: "Salutations"}
gVal := reflect.ValueOf(greeting)
// not a pointer so all we can do is read it
fmt.Println(gVal.Interface())
gpVal := reflect.ValueOf(&greeting)
// it’s a pointer, so we can change it, and it changes the underlying variable
gpVal.Elem().SetString("goodbye")
fmt.Println(greeting)
fType := reflect.TypeOf(f)
fVal := reflect.New(fType)
fVal.Elem().Field(0).SetInt(20)
fVal.Elem().Field(1).SetString("Greetings")
f2 := fVal.Elem().Interface().(Foo)
fmt.Printf("%+v, %d, %s\n", f2, f2.A, f2.B)
}
Результат выглядит следующим образом:
hello
goodbye
{A:20 B:Greetings}, 20, Greetings
Создать экземпляр без make
Для таких типов, как срез, карта и канал, которые необходимо создать с помощью make, вы также можете использовать отражение. Используйте Reflect.MakeSlice для среза, Reflect.MakeMap для карты и Reflect.MakeChan для канала Вам необходимо указать тип создаваемой переменной, т. е. Reflect.Type, для передачи этим функциям. После успешного вызова вы получите переменную типа Reflect.Value, этой переменной можно манипулировать через отражение, а после завершения операции преобразовать в обычную переменную.
func main() {
// declaring these vars, so I can make a reflect.Type
intSlice := make([]int, 0)
mapStringInt := make(map[string]int)
// here are the reflect.Types
sliceType := reflect.TypeOf(intSlice)
mapType := reflect.TypeOf(mapStringInt)
// and here are the new values that we are making
intSliceReflect := reflect.MakeSlice(sliceType, 0, 0)
mapReflect := reflect.MakeMap(mapType)
// and here we are using them
v := 10
rv := reflect.ValueOf(v)
intSliceReflect = reflect.Append(intSliceReflect, rv)
intSlice2 := intSliceReflect.Interface().([]int)
fmt.Println(intSlice2)
k := "hello"
rk := reflect.ValueOf(k)
mapReflect.SetMapIndex(rk, rv)
mapStringInt2 := mapReflect.Interface().(map[string]int)
fmt.Println(mapStringInt2)
}
Результат выглядит следующим образом:
[10]
map[hello:10]
создать функцию
Вы можете не только создать пространство для хранения данных с помощью отражения, но и создать новые функции с помощью функции Reflect.MakeFunc, предоставляемой отражением. Эта функция ожидает получить два параметра: один тип Reflect.Type, а Kind — функция, а другой — функция закрытия, тип входного параметра — []reflect.Value, а выходной параметр — []reflect.Value.
Вот краткий практический пример обертывания любой функции функцией, которая записывает время выполнения.
func MakeTimedFunction(f interface{}) interface{} {
rf := reflect.TypeOf(f)
if rf.Kind() != reflect.Func {
panic("expects a function")
}
vf := reflect.ValueOf(f)
wrapperF := reflect.MakeFunc(rf, func(in []reflect.Value) []reflect.Value {
start := time.Now()
out := vf.Call(in)
end := time.Now()
fmt.Printf("calling %s took %v\n", runtime.FuncForPC(vf.Pointer()).Name(), end.Sub(start))
return out
})
return wrapperF.Interface()
}
func timeMe() {
fmt.Println("starting")
time.Sleep(1 * time.Second)
fmt.Println("ending")
}
func timeMeToo(a int) int {
fmt.Println("starting")
time.Sleep(time.Duration(a) * time.Second)
result := a * 2
fmt.Println("ending")
return result
}
func main() {
timed := MakeTimedFunction(timeMe).(func())
timed()
timedToo := MakeTimedFunction(timeMeToo).(func(int) int)
fmt.Println(timedToo(2))
}
Результат выглядит следующим образом:
starting
ending
calling main.timeMe took 1s
starting
ending
calling main.timeMeToo took 2s
4
Создать новую структуру
В Go отражение также может создать совершенно новую структуру во время выполнения, что вы можете сделать, передав фрагмент Reflect.StructField в функцию Reflect.StructOf. Звучит абсурдно, мы создаем новый тип, но у этого типа нет имени, поэтому его нельзя преобразовать в обычную переменную. Вы можете создать из него экземпляр и использовать Interface() для передачи его значения в переменную типа interface{}, но если вы хотите установить его значение, вы должны сделать это путем отражения.
func MakeStruct(vals ...interface{}) interface{} {
var sfs []reflect.StructField
for k, v := range vals {
t := reflect.TypeOf(v)
sf := reflect.StructField{
Name: fmt.Sprintf("F%d", (k + 1)),
Type: t,
}
sfs = append(sfs, sf)
}
st := reflect.StructOf(sfs)
so := reflect.New(st)
return so.Interface()
}
func main() {
s := MakeStruct(0, "", []int{})
// this returned a pointer to a struct with 3 fields:
// an int, a string, and a slice of ints
// but you can’t actually use any of these fields
// directly in the code; you have to reflect them
sr := reflect.ValueOf(s)
// getting and setting the int field
fmt.Println(sr.Elem().Field(0).Interface())
sr.Elem().Field(0).SetInt(20)
fmt.Println(sr.Elem().Field(0).Interface())
// getting and setting the string field
fmt.Println(sr.Elem().Field(1).Interface())
sr.Elem().Field(1).SetString("reflect me")
fmt.Println(sr.Elem().Field(1).Interface())
// getting and setting the []int field
fmt.Println(sr.Elem().Field(2).Interface())
v := []int{1, 2, 3}
rv := reflect.ValueOf(v)
sr.Elem().Field(2).Set(rv)
fmt.Println(sr.Elem().Field(2).Interface())
}
Результат выглядит следующим образом:
0
20
reflect me
[]
[1 2 3]
Ограничения отражения
Рефлексия имеет большое ограничение. Хотя вы можете создавать новые функции с отражением во время выполнения, вы не можете создавать новые методы с отражением, что означает, что вы не можете реализовать интерфейс с отражением во время выполнения, а структуры, созданные с отражением, очень фрагментированы для использования. Более того, структура, созданная отражением, не может реализовать функцию режима GO-делегирования через анонимные поля.
Давайте рассмотрим пример реализации шаблона делегата через структуру.Обычно поля структуры имеют определенные имена. В этом примере мы определяем два типа, Foo и Bar:
type Foo struct {
A int
}
func (f Foo) Double() int {
return f.A * 2
}
type Bar struct {
Foo
B int
}
type Doubler interface {
Double() int
}
func DoDouble(d Doubler) {
fmt.Println(d.Double())
}
func main() {
f := Foo{10}
b := Bar{Foo: f, B: 20}
DoDouble(f) // passed in an instance of Foo; it meets the interface, so no surprise here
DoDouble(b) // passed in an instance of Bar; it works!
}
Код показывает, что поле Foo в Bar не имеет имени, что делает его анонимным или встроенным полем. Bar также удовлетворяет интерфейсу Double, хотя только Foo реализует метод Double, возможность, известную как делегирование. Во время компиляции Go автоматически сгенерирует методы в Foo для Bar. Это не наследование, и если вы попытаетесь передать Bar функции, которая принимает только Foo, компиляция не пройдет.
Если вы используете отражение для создания встроенного поля и пытаетесь получить доступ к его методам, произойдет очень странное поведение. Лучший способ, мы не используем его. Что касается этой проблемы, вы можете посмотреть две проблемы на github,issue/15924а такжеissues/16522. К сожалению, они еще не добились никакого прогресса.
Итак, в чем проблема с этим? Чего мы можем достичь, если будем поддерживать динамические интерфейсы? Как упоминалось ранее, мы можем создавать функции с помощью отражения Go и реализовывать функции-оболочки, которые также могут быть реализованы через интерфейсы. В Java это называется динамическим прокси. В сочетании с аннотациями вы можете получить очень мощную возможность переключаться с императивного программирования на декларативное.JDBI, эта библиотека Java позволяет определить интерфейс на уровне DAO, SQL-запрос которого определяется с помощью аннотаций. Весь код манипулирования данными динамически генерируется во время выполнения, что очень эффективно.
не имеет значения
Даже с этим ограничением отражение — мощный инструмент, которым должен овладеть каждый разработчик Go. Но как мы можем использовать это с пользой,Следующий,Английский оригиналВ сообщении блога я рассмотрю использование отражения через некоторые библиотеки и буду использовать его для реализации некоторых функций.