#go #interface #clone
#Вперед #указатели #интерфейс
Вопрос:
Как скопировать значение интерфейса в Go?
Мой User
интерфейс:
type User interface {
Name() string
SetName(name string)
}
Моя Admin
структура:
type Admin struct {
name string
}
func (a *Admin) Name() string {
return a.name
}
func (a *Admin) SetName(name string) {
a.name = name
}
Я пытаюсь скопировать user1
значение.
Основная функция:
func main() {
var user1 User
user1 = amp;Admin{name:"user1"}
fmt.Printf("User1's name: %sn", user1.Name())
var user2 User
user2 = user1
user2.SetName("user2")
fmt.Printf("User2's name: %sn", user2.Name()) // The name will be changed as "user2"
fmt.Printf("User1's name: %sn", user1.Name()) // The name will be changed as "user2" too, How to make the user1 name does not change?
}
Как добиться того, чтобы при изменении имени копии оригинал не менялся?
Ответ №1:
Проблема здесь в том, что ваша user1
переменная (которая имеет тип User
) содержит указатель на Admin
структуру.
Когда вы присваиваете user1
другой переменной (типа User
), значение интерфейса, представляющее собой пару динамического типа и значения (value;type)
, будет скопировано — таким образом, будет скопирован указатель, который будет указывать на ту же Admin
структуру. Таким образом, у вас есть только одно Admin
значение структуры, оба user1
и user2
ссылаются (указывают) на это. Изменение его с помощью любого из значений интерфейса изменяет одно-единственное значение.
Чтобы сделать user1
и user2
различными, вам нужны 2 «базовых» Admin
структуры.
Один из способов — ввести assert значение в user1
значение интерфейса и создать копию этой структуры и преобразовать ее адрес в другое User
значение:
var user2 User
padmin := user1.(*Admin) // Obtain *Admin pointer
admin2 := *padmin // Make a copy of the Admin struct
user2 = amp;admin2 // Wrap its address in another User
user2.SetName("user2")
Теперь они будут различаться, выводиться (попробуйте это на игровой площадке Go):
User1's name: user1
User2's name: user2
User1's name: user1
Конечно, у этого решения есть свое ограничение: динамический тип, сохраненный в User
значении интерфейса, является «проводным» в решении ( *Admin
).
Использование отражения
Если нам нужно «общее» решение (а не только то, которое работает с *Admin
), мы можем использовать отражение ( reflect
пакет).
Для простоты давайте предположим, что user1
всегда содержит указатель (на данный момент).
Используя отражение, мы можем получить динамический тип (здесь *Admin
) и даже динамический тип без указателя ( Admin
). И мы можем использовать reflect.New()
для получения указателя на новое значение этого типа (чей тип будет идентичен исходному динамическому типу в user1
— *Admin
) и обернуть это обратно в User
. Вот как это могло бы выглядеть:
var user3 User
user3 = reflect.New(reflect.ValueOf(user1).Elem().Type()).Interface().(User)
user3.SetName("user3")
Вывод (попробуйте это на игровой площадке Go):
User1's name: user1
User3's name: user3
User1's name: user1
Обратите внимание, что reflect.New()
будет создано новое значение, которое инициализируется его нулевым значением (поэтому оно не будет копией оригинала). Здесь это не проблема, поскольку Admin
имеет только одно поле, которое мы все равно собираемся изменить, но в целом мы должны помнить об этом.
Нашим первоначальным предположением было, что user1
содержит указатель. Теперь «полное» решение не должно делать такого предположения. Если значение в user1
не было бы указателем, вот как его можно было бы «клонировать»:
var user3 User
if reflect.TypeOf(user1).Kind() == reflect.Ptr {
// Pointer:
user3 = reflect.New(reflect.ValueOf(user1).Elem().Type()).Interface().(User)
} else {
// Not pointer:
user3 = reflect.New(reflect.TypeOf(user1)).Elem().Interface().(User)
}
user3.SetName("user3")
Комментарии:
1. Дополнительно, предположим user1.Name является «ПОЛЬЗОВАТЕЛЬСКИМ», пользователь2 = reflect.New(reflect.valueOf(пользователь1).Elem().Type()). Интерфейс(). (Пользователь) The user2.Name это пустая строка, как сделать ее такой же, как user1?
Ответ №2:
Другим решением может быть добавление метода Clone () в ваш пользовательский интерфейс:
type User interface {
Clone() User
Name() string
SetName(name string)
}
type Admin struct {
name string
}
func (a *Admin) Clone() User {
u := *a
return amp;u
}
func (a *Admin) Name() string {
return a.name
}
func (a *Admin) SetName(name string) {
a.name = name
}
func main() {
var user1 User
user1 = amp;Admin{name:"user1"}
fmt.Printf("User1's name: %sn", user1.Name())
var user2 User
user2 = user1.Clone()
user2.SetName("user2")
fmt.Printf("User2's name: %sn", user2.Name())
// output: User2's name: user2
fmt.Printf("User1's name: %sn", user1.Name())
// output: User1's name: user1
}
Конечно, в этом случае реализация привязывается к интерфейсу, что может быть нежелательно, но это решение.
Ответ №3:
Есть более общий способ сделать это.
func Clone(oldObj interface{}) interface{} {
newObj := reflect.New(reflect.TypeOf(oldObj).Elem())
oldVal := reflect.ValueOf(oldObj).Elem()
newVal := newObj.Elem()
for i := 0; i < oldVal.NumField(); i {
newValField := newVal.Field(i)
if newValField.CanSet() {
newValField.Set(oldVal.Field(i))
}
}
return newObj.Interface()
}
Однако у него есть одна ошибка: он не может установить неэкспортируемые поля. Это можно обойти, используя это решение с помощью unsafe
черной магии, но я бы предпочел избежать этого.
Ответ №4:
Самый простой способ сделать это — использовать библиотеку (я бы рекомендовал https://github.com/jinzhu/copier) в сочетании с некоторой магией отражения:
var cloneObj MyCustomStruct = reflect.New(reflect.ValueOf(sourceObj).Elem().Type()).Interface()
copier.Copy(cloneObj, sourceObj)
Это дает вам полную копию правильно введенной исходной структуры (что важно). Если вы не используете интерфейс, вам может потребоваться немного настроить использование отражения (удалить .Interface()
).