2013-10-11 3 views
1

(В следующем вопросе: nested struct initialization literals).Вложенная структура - получить «базовую» структуру

Теперь, когда я могу инициализировать структуру литералами, которые легко писать, я позже в своем коде нуждаюсь в доступе к родительской структуре, но не знаю конкретного производного типа. Это как это:

type A struct { 
    MemberA string 
} 

type B struct { 
    A 
    MemberB string 
} 

А потом использовать его как это:

b := B { 
    A: A { MemberA: "test1" }, 
    MemberB: "test2", 
} 
fmt.Printf("%+v\n", b) 

var i interface{} = b 

// later in the code, I only know that I have something that has a nested A, 
// I don't know about B (because A is in a library and B is in the 
// calling code that uses the library). 

// so I want to say "give me the A out of i", so I try a type assertion 

if a, ok := i.(A); ok { 
    fmt.Printf("Yup, A is A: %+v\n", a) 
} else { 
    fmt.Printf("Aristotle (and John Galt) be damned! A is NOT A\n") 
} 

// no go 

Опции, я вижу, являются:

  • Я мог бы использовать отражение искать члена под названием " A "и, если предположить, что это правильный тип, используйте его. Это будет работоспособным, но менее эффективным и, безусловно, более «неуклюжим».

  • Я мог бы потребовать от абонента реализовать интерфейс (например, HasA { Aval() A } или аналогичный, который возвращает экземпляр А. До сих пор это самая лучшая идея, я мог думать.

  • Другое дело, что я мог бы просто получим, что вызывающий абонент передает значение А (т.е. в приведенном выше примере, var i interface{} = b становится var i A = b.A). Но то, что происходит, я фактически динамически перебираю членов B и делаю с ними все, поэтому мне нужен этот более «производный» тип в порядке к этому. (Я опустил это из вопроса, потому что это скорее просто справочная информация о том, почему я столкнулся с этим и не уместен для технического ответа на вопрос.)

Было бы здорово, если бы я мог просто «бросить его на А», как в Java. Есть ли более элегантный способ сделать это.

ответ

2

Было бы замечательно, если бы я мог просто «бросить его в», как вы будет в Java. Есть ли более элегантный способ сделать это.

Слепое литье - это почти всегда плохая новость для вашего качества кода, поэтому на самом деле это довольно неплохо. , чтобы это было трудно сделать.

Я бы пошел по интерфейсу, так как это также устранит interface{}, который вы используете для хранения b. Если у вас действительно есть большое разнообразие типов, и они разделяют только то, что они вставляют A интерфейс , который предлагает AVal() A, кажется мне хорошим.

В качестве бонуса вы можете определить AVal() A на A, так что вам не нужно его реализовывать для каждого типа, который включает A.

Пример (on play):

type A struct { 
    MemberA string 
} 

func (a *A) AVal() *A { 
    return a 
} 

type B struct { 
    *A 
    MemberB string 
} 

type AEmbedder interface { 
    AVal() *A 
} 

func main() { 
    b := B { 
      A: &A { MemberA: "test1" }, 
      MemberB: "test2", 
    } 

    var i AEmbedder = b 

    a := i.AVal() 
    fmt.Printf("Yup, A is A: %+v\n", a) 
} 

Обратите внимание, что я сейчас, используя значение указателя, так что AVal() *A не возвращает копию, но соответствующий экземпляр A.

+0

Я вижу. Поскольку A реализует AEmbedder, то B, путем вложенности делает также ... Это кажется довольно выполнимым и простым. Это также означает, что когда вы выполняете 'var i AEmbedder = b', тип времени выполнения значения в' i' по-прежнему B - так что итерация по его полям и т. Д. Дает мне результаты из более производного времени. Довольно рад. Я думаю, что это именно то, что я хочу. (Если я вообще не смогу избежать этого, как вы упомянули). –

0

Это сработало для меня. Хотя это не так, что вы делаете прямое литье, оно дает правильный экземпляр объекта

fmt.Printf("It's", i.(B).A) 

Надеюсь, это поможет!

2

Если у вас есть неизвестный тип b, единственный способ выкопать встроенное поле - через отражение.

Это не что неуклюжим:

// obviously missing various error checks 
t := reflect.ValueOf(i) 
fmt.Printf("%+v\n", t.FieldByName("A").Interface().(A)) 

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

Я думаю, что самый чистый способ справиться с этой ситуацией - использовать общий интерфейс с соответствующими методами доступа для полей, которые вы хотите обработать. Вы увидите примеры этого stdlib, например. http.ResponseWriter, который имеет метод Header(), используемый для доступа к фактическим заголовкам ответа.

+0

Спасибо Джим - вы правы. Сама проблема и попытка ее решить путем вложенности структур более сложна по своей сути, чем решение - этот сниппет вполне справляется с учетом проблемы. Получил это от других применений шаблона интерфейса в std, это тоже хороший момент. –

0

Игра вокруг с ним немного, я получил эту работу: http://play.golang.org/p/-bbHZr-0xx

Я не 100% уверен, почему это работает, но моя лучшая теория, что, когда вы звоните

a := i.(A) 

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

a := i.(B).A 
+0

Вы утверждаете, что 'i' является' B', используя 'i. (B)'. После этого вы можете запросить 'A' из этого утверждённого значения, так как он является членом' B'. Здесь нет волшебства. К сожалению, это требует, чтобы вы тестировали 'i' для каждого возможного типа, так как' B' может быть не единственным типом, имеющим 'A'. Следовательно, этот метод может быть не лучшим. – nemo

+0

@nemo - да, это именно то, с чем я сталкиваюсь. Я не хочу, чтобы этот код «знал» о B, потому что B фактически создан в другом пакете, который использует пакет, где A живет как библиотека. –

+0

Лучшие варианты: а) избегают этой ситуации; б) используют интерфейсы; в) используют отражение (в этом порядке). – nemo

Смежные вопросы