Как скопировать значение интерфейса в Go?

#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() ).