Размышления Голанга, глубокое понимание и примеры

задняя часть Go API дизайн

[TOC]

Размышления Голанга, глубокое понимание и примеры

【Записано в феврале 2018 года】

Концепция отражения в языках программирования

В компьютерных науках рефлексия относится к классу приложений, которые описывают себя и контролируют себя. Другими словами, этот тип приложения реализует самопредставление и исследование своего собственного поведения, принимая определенный механизм, и может корректировать или изменять состояние и состояние поведения, описываемого приложением, в соответствии с состоянием и результатом его работы. собственное поведение родственная семантика.

Модель отражения для каждого языка разная, а некоторые языки вообще не поддерживают отражение. Язык Golang реализует отражение. Механизм отражения заключается в динамическом вызове методов и свойств объектов во время выполнения. Официальный пакет отражения связан с отражением, и его можно использовать, пока этот пакет включен.

Еще одно слово, gRPC в Golang также реализуется посредством отражения.

интерфейс и отражение

Прежде чем говорить об отражении, давайте рассмотрим некоторые принципы шрифтового дизайна в Golang.

  • Переменные включают (тип, значение) две части

    • Поймите это, чтобы понять, почему nil != nil
  • Тип включает в себя статический тип и конкретный тип. Короче говоря, статический тип — это тип, который вы видите в коде (например, int, string), а конкретный тип — это тип, который видит система среды выполнения.

  • Успешность утверждения типа зависит от конкретного типа переменной, а не от статического типа, поэтому переменная чтения также может быть подтверждена типом как записывающая, если ее конкретный тип также реализует метод записи.

Отражение, которое будет обсуждаться далее, основано на типе.Тип переменной указанного типа в Golang является статическим (то есть переменная указанного типа, такая как int и string, ее тип является статическим типом), когда создается переменная Было установлено, что отражение в основном связано с типом интерфейса Голанга (его тип - конкретный тип), и только тип интерфейса имеет отражение.

В реализации Golang каждой переменной интерфейса соответствует пара, и в паре записывается значение и тип фактической переменной:

(value, type)

value — фактическое значение переменной, а type — тип фактической переменной. Переменная типа interface{} содержит два указателя, один указатель указывает на тип значения [соответствует конкретному типу], а другой указатель указывает на фактическое значение [соответствует значению].

Например, создайте переменную типа *os.File и назначьте ее переменной интерфейса r:

tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)

var r io.Reader
r = tty

В пару интерфейсной переменной r будет записана следующая информация: (tty, *os.File), эта пара неизменна при непрерывном присвоении интерфейсной переменной, а интерфейсная переменная r присваивается другой интерфейсной переменной w:

var w io.Writer
w = r.(io.Writer)

Пара интерфейсной переменной w такая же, как и пара r, обе: (tty, *os.File), даже если w — пустой интерфейсный тип, пара остается неизменной.

Существование интерфейса и его пары является предпосылкой отражения в Golang.Понимание пары облегчает понимание отражения. Отражение — это механизм, используемый для обнаружения пар, хранящихся внутри переменных интерфейса (значение; тип, конкретный тип).

отражение Голанга

Основные функции Reflect TypeOf и ValueOf

Поскольку отражение — это механизм, используемый для обнаружения пар пар (значение-значение; тип-конкретный тип), хранящихся в переменных интерфейса. Так как же в пакете отражения Golang отобразить, что позволяет нам напрямую получать информацию внутри переменной? Он предоставляет два типа (или два метода), которые позволяют нам легко получить доступ к содержимому переменных интерфейса, Reflect.ValueOf() и Reflect.TypeOf(), см. официальное объяснение

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i.  ValueOf(nil) returns the zero 
func ValueOf(i interface{}) Value {...}

翻译一下:ValueOf用来获取输入参数接口中的数据的值,如果接口为空则返回0


// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {...}

翻译一下:TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空则返回nil

Reflect.TypeOf() получает тип в паре, а Reflect.ValueOf() получает значение в паре Пример выглядит следующим образом:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var num float64 = 1.2345

	fmt.Println("type: ", reflect.TypeOf(num))
	fmt.Println("value: ", reflect.ValueOf(num))
}

运行结果:
type:  float64
value:  1.2345

инструкция

  1. Reflect.TypeOf: напрямую дает тип, который нам нужен, например, float64, int, различные указатели, структуры и другие реальные типы.

  2. Reflect.ValueOf: непосредственно дает конкретное значение, которое мы хотим, например конкретное значение 1,2345 или значение структурной структуры, такой как &{1 "Allen.Wu" 25}

  3. То есть ясно, что отражение может преобразовать «переменную типа интерфейса» в «объект типа отражения». Тип отражения относится к двум типам Reflect.Type и Reflect.Value.

Получить информацию об интерфейсе из reffect.Value

При выполнении Reflect.ValueOf(interface) получается переменная типа "relfect.Value", а реальное содержимое переменной интерфейса может быть получено с помощью собственного метода Interface(), а затем может быть преобразовано с помощью оценки типа. и преобразованы в оригинал Есть настоящие типы. Однако мы можем знать исходный тип или не знать исходный тип, поэтому объясняются следующие два случая.

Известный исходный тип [выполнить «принуждение»]

Метод преобразования известного типа в соответствующий ему тип выглядит следующим образом: непосредственно через интерфейсный метод, а затем принудительно:

realValue := value.Interface().(已知的类型)

Пример выглядит следующим образом:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var num float64 = 1.2345

	pointer := reflect.ValueOf(&num)
	value := reflect.ValueOf(num)

	// 可以理解为“强制转换”,但是需要注意的时候,转换的时候,如果转换的类型不完全符合,则直接panic
	// Golang 对类型要求非常严格,类型一定要完全符合
	// 如下两个,一个是*float64,一个是float64,如果弄混,则会panic
	convertPointer := pointer.Interface().(*float64)
	convertValue := value.Interface().(float64)

	fmt.Println(convertPointer)
	fmt.Println(convertValue)
}

运行结果:
0xc42000e238
1.2345
инструкция
  1. При преобразовании, если преобразованный тип не полностью согласован, сразу паникуйте, а требования к типу очень строгие!
  2. При преобразовании надо различать указатель это или указатель
  3. То есть отражение может повторно преобразовывать «объекты типа отражения» в «переменные типа интерфейса».

Неизвестный тип оригинала [перейдите, чтобы обнаружить его в файле]

Во многих случаях мы можем не знать его конкретный тип, так что же нам делать в это время? Нам нужно пройти и обнаружить его Filed, чтобы узнать, пример выглядит следующим образом:

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Id   int
	Name string
	Age  int
}

func (u User) ReflectCallFunc() {
	fmt.Println("Allen.Wu ReflectCallFunc")
}

func main() {

	user := User{1, "Allen.Wu", 25}

	DoFiledAndMethod(user)

}

// 通过接口来获取任意参数,然后一一揭晓
func DoFiledAndMethod(input interface{}) {

	getType := reflect.TypeOf(input)
	fmt.Println("get Type is :", getType.Name())

	getValue := reflect.ValueOf(input)
	fmt.Println("get all Fields is:", getValue)

	// 获取方法字段
	// 1. 先获取interface的reflect.Type,然后通过NumField进行遍历
	// 2. 再通过reflect.Type的Field获取其Field
	// 3. 最后通过Field的Interface()得到对应的value
	for i := 0; i < getType.NumField(); i++ {
		field := getType.Field(i)
		value := getValue.Field(i).Interface()
		fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
	}

	// 获取方法
	// 1. 先获取interface的reflect.Type,然后通过.NumMethod进行遍历
	for i := 0; i < getType.NumMethod(); i++ {
		m := getType.Method(i)
		fmt.Printf("%s: %v\n", m.Name, m.Type)
	}
}

运行结果:
get Type is : User
get all Fields is: {1 Allen.Wu 25}
Id: int = 1
Name: string = Allen.Wu
Age: int = 25
ReflectCallFunc: func(main.User)

инструкция

Из текущего результата мы можем узнать, что шаги для получения конкретных переменных и типов интерфейсов неизвестных типов:

  1. Сначала получите Reflect.Type интерфейса, а затем пройдите через NumField
  2. Затем получить его поле через поле Reflect.Type
  3. Наконец, получите соответствующее значение через интерфейс Field().

Из запущенного результата вы можете узнать, что шаги для получения метода (функции) интерфейса неизвестного типа:

  1. Сначала получите Reflect.Type интерфейса, а затем пройдите через NumMethod.
  2. Затем получите соответствующий реальный метод (функцию) через метод Reflect.Type соответственно.
  3. Наконец, возьмите имя и тип результата, чтобы узнать имя конкретного метода.
  4. То есть отражение может повторно преобразовывать «объекты типа отражения» в «переменные типа интерфейса».
  5. Вложение struct или struct — это один и тот же метод обработки суждений.

Установите значение фактической переменной через Reflect.Value

Reflect.Value получается с помощью Reflect.ValueOf (X). Только когда X является указателем, значение фактической переменной X может быть изменено с помощью Reflect.Value, то есть для изменения объекта типа отражения вы должны убедиться, что его значение "адресуемое".

Пример выглядит следующим образом:

package main

import (
	"fmt"
	"reflect"
)

func main() {

	var num float64 = 1.2345
	fmt.Println("old value of pointer:", num)

	// 通过reflect.ValueOf获取num中的reflect.Value,注意,参数必须是指针才能修改其值
	pointer := reflect.ValueOf(&num)
	newValue := pointer.Elem()

	fmt.Println("type of pointer:", newValue.Type())
	fmt.Println("settability of pointer:", newValue.CanSet())

	// 重新赋值
	newValue.SetFloat(77)
	fmt.Println("new value of pointer:", num)

	////////////////////
	// 如果reflect.ValueOf的参数不是指针,会如何?
	pointer = reflect.ValueOf(num)
	//newValue = pointer.Elem() // 如果非指针,这里直接panic,“panic: reflect: call of reflect.Value.Elem on float64 Value”
}

运行结果:
old value of pointer: 1.2345
type of pointer: float64
settability of pointer: true
new value of pointer: 77

инструкция

  1. Параметр, который необходимо передать, — это указатель *float64, а затем вы можете получить значение, на которое указывает pointer.Elem(),Обратите внимание, что это должен быть указатель.
  2. Если переданный параметр не указатель, а переменная, то
    • Получить объект, соответствующий исходному значению, через Elem и паниковать напрямую
    • Запрос, может ли он быть установлен методом CanSet, возвращает false
  3. newValue.CantSet() указывает, можно ли сбросить его значение. Если вывод верен, его можно изменить. В противном случае его нельзя изменить. После модификации распечатайте его и обнаружите, что оно было изменено.
  4. Reflect.Value.Elem() означает получение объекта отражения, соответствующего исходному значению, может быть изменен только исходный объект, текущий объект отражения не может быть изменен
  5. То есть, если вы хотите изменить объект типа отражения, его значение должно быть «адресуемым» [должен быть передан соответствующий указатель, а объект отражения, соответствующий исходному значению, должен быть получен с помощью метода Elem]
  6. Вложение struct или struct — это один и тот же метод обработки суждений.

Вызов метода через Reflect.ValueOf

Это расширенное использование.Ранее мы говорили только об использовании нескольких отражений типов и переменных, в том числе о том, как получить их значения, их типы и как сбросить новые значения. Но в инженерных приложениях другим распространенным и расширенным использованием является вызов метода [функции] через отражение. Например, когда мы хотим сделать каркасный проект, нам нужно расширить метод по желанию, или пользователь может настроить метод, так какие же средства мы используем для расширения метода, чтобы пользователь мог его настроить? Ключевым моментом является то, что пользовательский метод пользователя неизвестен, поэтому мы можем сделать это, отразив

Пример выглядит следующим образом:

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Id   int
	Name string
	Age  int
}

func (u User) ReflectCallFuncHasArgs(name string, age int) {
	fmt.Println("ReflectCallFuncHasArgs name: ", name, ", age:", age, "and origal User.Name:", u.Name)
}

func (u User) ReflectCallFuncNoArgs() {
	fmt.Println("ReflectCallFuncNoArgs")
}

// 如何通过反射来进行方法的调用?
// 本来可以用u.ReflectCallFuncXXX直接调用的,但是如果要通过反射,那么首先要将方法注册,也就是MethodByName,然后通过反射调动mv.Call

func main() {
	user := User{1, "Allen.Wu", 25}
	
	// 1. 要通过反射来调用起对应的方法,必须要先通过reflect.ValueOf(interface)来获取到reflect.Value,得到“反射类型对象”后才能做下一步处理
	getValue := reflect.ValueOf(user)

	// 一定要指定参数为正确的方法名
	// 2. 先看看带有参数的调用方法
	methodValue := getValue.MethodByName("ReflectCallFuncHasArgs")
	args := []reflect.Value{reflect.ValueOf("wudebao"), reflect.ValueOf(30)}
	methodValue.Call(args)

	// 一定要指定参数为正确的方法名
	// 3. 再看看无参数的调用方法
	methodValue = getValue.MethodByName("ReflectCallFuncNoArgs")
	args = make([]reflect.Value, 0)
	methodValue.Call(args)
}


运行结果:
ReflectCallFuncHasArgs name:  wudebao , age: 30 and origal User.Name: Allen.Wu
ReflectCallFuncNoArgs

инструкция

  1. Чтобы вызвать соответствующий метод через отражение, вы должны сначала получить Reflect.Value через Reflect.ValueOf (интерфейс), а затем перейти к следующему шагу после получения «объекта типа отражения»

  2. Reflect.Value.MethodByName Этот .MethodByName должен указать точное и реальное имя метода.Если есть ошибка, он будет паниковать напрямую.MethodByName возвращает имя метода Reflect.Value, соответствующее значению функции.

  3. []reflect.Value, это параметр метода, который нужно вызвать напоследок, их может быть ни одного, либо один или несколько, в зависимости от фактических параметров.

  4. Метод Call для Reflect.Value в конечном итоге вызовет реальный метод. Параметры должны быть согласованы. Если Reflect.Value'Kind не является методом, он будет вызывать панику напрямую.

  5. Его можно вызвать напрямую с помощью u.ReflectCallFuncXXX, но если вы хотите использовать отражение, вы должны сначала зарегистрировать метод, то есть MethodByName, а затем вызывать methodValue.Call через отражение

Производительность отражения Голанга

Отражение Golang очень медленное, это связано с дизайном его API. В Java мы обычно используем для этого отражение.

Field field = clazz.getField("hello");
field.get(obj1);
field.get(obj2);

Полученный тип объекта отражения — java.lang.reflect.Field. Он многоразовый. Пока передается другой объект, можно получить соответствующее поле этого объекта.

Но отражение Голанга устроено иначе:

type_ := reflect.TypeOf(obj)
field, _ := type_.FieldByName("hello")

Взятый здесь объект поля относится к типу Reflect.StructField, но у него нет способа получить значение соответствующего объекта. Если вы хотите получить значение, вам нужно использовать другой набор отражений для объекта вместо типа

type_ := reflect.ValueOf(obj)
fieldValue := type_.FieldByName("hello")

Выбранный здесь тип fieldValue — Reflect.Value, который является конкретным значением, а не повторно используемым объектом отражения.Каждое отражение требует структуры Reflect.Value malloc, а также включает сборщик мусора.

резюме

Golang Reflect работает медленно по двум основным причинам.

  1. Включает выделение памяти и последующую сборку мусора;

  2. В реализации Reflect есть большое количество перечислений, то есть циклов for, таких как типы.

Суммировать

Вышеизложенное подробно описывает различные функции и использование отражения Голанга, и есть соответствующие примеры.Я считаю, что это можно практиковать в инженерных приложениях.Подводя итог:

  • Рефлексия может значительно повысить гибкость программы, предоставляя интерфейсу{} больше возможностей для игры.

    • Отражение должно сочетаться с интерфейсом, чтобы играть хорошо
    • Если тип переменной имеет конкретный тип (то есть переменная интерфейса), происходит только отражение.
  • Отражение может преобразовать «переменную типа интерфейса» в «объект типа отражения».

    • Отражение использует функции TypeOf и ValueOf для получения информации о целевом объекте из интерфейсов.
  • Отражение может преобразовать «объект типа отражения» в «переменную типа интерфейса».

    • Reflect.value.Interface().(известный тип)
    • Пройдите через поле Reflect.Type, чтобы получить его поле
  • Отражение может изменить объект типа отражения, но его значение должно быть «адресуемым».

    • Если вы хотите использовать отражение для изменения состояния объекта, предпосылка заключается в том, что interface.data устанавливается, то есть указатель-интерфейс
  • Методы можно вызывать «динамически» через отражение

  • Поскольку сам Golang не поддерживает шаблоны, часто необходимо использовать отражение в сценариях, где шаблоны использовались в прошлом.

Справочная ссылка

["Добро пожаловать, обратите внимание на мою общедоступную учетную запись WeChat: разработка серверной системы Linux, и позже я буду активно отправлять высококачественные статьи через общедоступную учетную запись WeChat"]

我的微信公众号