One place for hosting & domains

      структур

      Определение структур в Go


      Введение

      Создание абстракций вокруг конкретных деталей — это самый лучший инструмент, который язык программирования может предоставить разработчику. Структуры позволяют разработчикам Go описывать мир, в котором работает программа Go. Вместо того, чтобы беспокоиться об использовании строк, описывающих Street, City или PostalCode, структуры позволяют нам говорить об Address. Они служат естественным связующим звеном для документирования в рамках наших усилий по информированию будущих разработчиков (включая нас самих) о том, какие данные важны для наших программ Go и как будущий код должен использовать эти данные надлежащим образом. Структуры могут быть определены и использованы несколькими способами. В этом обучающем руководстве мы рассмотрим каждый из этих способов.

      Определение структур

      Структуры работают как бумажные формы, которые вы можете использовать, например, для записи налогов. Бумажные формы могут иметь поля для текстовой информации, например, ваших имени и фамилии. Помимо текстовых полей, в формах могут быть флажки для булевых значений, например, “женат”, “холост”, или поля даты, например, даты рождения. Аналогичным образом структуры собирают различные элементы данных вместе и организуют их согласно различным именам полей. Инициализацию переменной с новой структурой можно сравнить с получением фотокопии бумажной формы, готовой к заполнению.

      Чтобы создать новую структуру, вы должны вначале предоставить Go проект, описывающий поля, которые содержит структура. Это определение структуры обычно начинается с ключевого слова type, после которого идет имя структуры. После этого нужно использовать ключевое слово struct, за которым следует пара скобок {}, где вы объявляете поля, которые будет содержать структура. После того как вы определили структуру, вы можете объявить переменные, которые используют это определение структуры. В данном примере определяется и используется структура:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name string
      }
      
      func main() {
          c := Creature{
              Name: "Sammy the Shark",
          }
          fmt.Println(c.Name)
      }
      

      При запуске этого кода вы увидите следующий вывод:

      output

      Sammy the Shark

      Сначала мы определяем структуру Creature в данном примере, которая содержит поле Name типа string. Внутри тела main мы создадим экземпляр Creature, поместив пару скобок после имени типа, Creature, а затем указав значения для полей этого экземпляра. Экземпляр в c будет иметь в поле Name значение “Sammy the Shark”. Внутри вызова функции fmt.Println мы будем получать значения поля экземпляра, указав точку после переменной, где был создан экземпляр, за которой следует имя поля, которое мы хотели бы получить. Например, c.Name в данном случае возвращает значение поля Name.

      При объявлении нового экземпляра структуры вы обычно перечисляете имена полей с их значениями, как показано в последнем примере. Кроме того, если каждое значение поля будет предоставлено во время создания экземпляра структуры, вы можете пропустить имена полей, как в данном примере:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name string
          Type string
      }
      
      func main() {
          c := Creature{"Sammy", "Shark"}
          fmt.Println(c.Name, "the", c.Type)
      }
      

      Вывод будет таким же, как в последнем примере:

      output

      Sammy the Shark

      Мы добавили дополнительное поле в Creature для отслеживания типа создания в виде строки. При создании экземпляра Creature внутри тела main мы предпочли использовать более короткую форму создания экземпляра, указав значения для каждого поля по порядку и опустив названия полей. В объявлении Creature{"Sammy", "Shark"} поле Name получает значение Sammy, а поле Type получает значение Shark, поскольку Name появляется первым в объявлении, а уже за ним идет Type.

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

      Имена полей в примерах выше начинались с заглавной буквы. Это не просто стилистическое предпочтение. Использование заглавных или строчных букв для имен полей определяет, будут ли ваши имена полей доступными для кода, запускаемого из других пакетов.

      Экспорт поля структуры

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

      package main
      
      import "fmt"
      
      type Creature struct {
          Name string
          Type string
      
          password string
      }
      
      func main() {
          c := Creature{
              Name: "Sammy",
              Type: "Shark",
      
              password: "secret",
          }
          fmt.Println(c.Name, "the", c.Type)
          fmt.Println("Password is", c.password)
      }
      

      Результат будет выглядеть так:

      output

      Sammy the Shark Password is secret

      Мы добавили дополнительное поле в наши предыдущие примеры, secret. secret — это неэкспортируемое поле типа string, что значит, что любой другой пакет, который пытается создать экземпляр Creature, не сможет получить доступ или задать значение для поля secret. Внутри этого пакета мы можем получить доступ к этим полям, как это сделано в данном примере. Поскольку main также находится в пакете main, эта функция может запросить c.password и получить сохраненное там значение. Неэкспортируемые поля в структурах встречаются часто, а доступ к таким полям осуществляется через экспортируемые методы.

      Вложенные структуры

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

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

      package main
      
      import "fmt"
      
      func main() {
          c := struct {
              Name string
              Type string
          }{
              Name: "Sammy",
              Type: "Shark",
          }
          fmt.Println(c.Name, "the", c.Type)
      }
      

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

      output

      Sammy the Shark

      Вместо того, чтобы определять новый тип, описывающий структуру с ключевым словом type, в данном примере вложенная структура определяется сразу же после короткого оператора присваивания :=. Мы определяем поля структуры, как и в предыдущих примерах, но теперь мы должны немедленно предоставить другую пару скобок и значения, которые будут присваиваться каждому полю. Использование структуры остается прежним, мы можем обратиться к именам полей с помощью записи с точкой. Чаще всего вы будете видеть вложенные структуры в тестах, так как часто используемые один раз структуры определяются для хранения данных и ожиданий для конкретного тестового случая.“”“

      Заключение

      Структуры — это коллекции разнородных данных, определенные программистами для организации информации. Большинство программ работают с огромными объемами данных, а без создания структур будет трудно вспомнить, какие переменные string или int принадлежали друг другу, а какие были разными. В следующий раз, когда вы будете танцевать с бубном вокруг групп переменных, спросите себя, может стоит объединить эти переменные с помощью структуры. Эти переменные также смогут лучше описать концепцию более высокого уровня.



      Source link

      Использование тегов структур в Go


      Введение

      Структуры используются для сбора различных элементов информации внутри одной единицы. Эти наборы информации используются для описания концепций более высокого уровня. Так, адрес состоит из области, города, улицы, почтового индекса и т. д. Когда вы считываете эту информацию из баз данных, API или других подобных систем, вы можете использовать теги структур для контроля присвоения этой информации в поля структуры. Структурные теги — это небольшие элементы метаданных, прикрепленные к полям структуры. Они содержат инструкции для другого кода Go, который работает с этой структурой.

      Как выглядит структурный тег?

      Структурные теги в Go представляют собой аннотации, которые отображаются после типа в декларации структуры Go. Каждый тег состоит из коротких строк, которым назначены определенные значения.

      Структурный тег выделяется символами апострофа “”` и выглядит следующим образом:

      type User struct {
          Name string `example:"name"`
      }
      

      Другой код Go может оценивать структуры и извлекать значения, назначенные определенным ключам, которые он запрашивает. Структурные теги не влияют на работу кода без кода, который их использует.

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

      package main
      
      import "fmt"
      
      type User struct {
          Name string `example:"name"`
      }
      
      func (u *User) String() string {
          return fmt.Sprintf("Hi! My name is %s", u.Name)
      }
      
      func main() {
          u := &User{
              Name: "Sammy",
          }
      
          fmt.Println(u)
      }
      

      Результат будет выглядеть так:

      Output

      Hi! My name is Sammy

      В этом примере определяется тип User с полем Name. Для поля Name назначен структурный тег example:"name". Мы ссылаемся на этот тег как на “структурный тег example”, поскольку в качестве ключа в нем используется слово example. Структурный тег example имеет значение "name" для поля Name. Для типа User мы также определим метод String(), который требуется для интерфейса fmt.Stringer. Он вызывается автоматически при передаче типа в fmt.Println и позволяет нам вывести хорошо отформатированную версию нашей структуры.

      В теле main мы создадим новый экземпляр типа User и передадим его в fmt.Println. Хотя в структуре имеется структурный тег, он не влияет на выполнение этого кода Go. Он выполняется точно так же, как если бы структурного тега не было.

      Чтобы использовать структурные теги для каких-либо целей, необходимо написать другой код Go, который будет запрашивать их во время исполнения. В стандартной библиотеке имеются пакеты, которые используют структурные теги в своей работе. Наиболее популярный из них — пакет encoding/json.

      Кодировка JSON

      JavaScript Object Notation (JSON) представляет собой текстовый формат кодирования наборов данных, организованных по различным ключам строк. Он обычно используется для обмена данными между разными программами, поскольку имеет достаточно простой формат для расшифровки библиотеками многих разных языков. Ниже приведен пример JSON:

      {
        "language": "Go",
        "mascot": "Gopher"
      }
      

      Этот объект JSON содержит два ключа, language и mascot. За этими ключами идут связанные с ними значения. Ключ language имеет значение Go, а ключу mascot присвоено значение Gopher.

      Кодировщик JSON в стандартной библиотеке использует структурные теги как аннотацию, указывая кодировщику, какие имена вы хотите присвоить полям в выводимых JSON результатах. Эти механизмы кодировки и декодировки JSON содержатся в пакете encoding/json.

      В этом примере показана кодировка JSON без структурных тегов:

      package main
      
      import (
          "encoding/json"
          "fmt"
          "log"
          "os"
          "time"
      )
      
      type User struct {
          Name          string
          Password      string
          PreferredFish []string
          CreatedAt     time.Time
      }
      
      func main() {
          u := &User{
              Name:      "Sammy the Shark",
              Password:  "fisharegreat",
              CreatedAt: time.Now(),
          }
      
          out, err := json.MarshalIndent(u, "", "  ")
          if err != nil {
              log.Println(err)
              os.Exit(1)
          }
      
          fmt.Println(string(out))
      }
      

      Этот код распечатывает следующее:

      Output

      { "Name": "Sammy the Shark", "Password": "fisharegreat", "CreatedAt": "2019-09-23T15:50:01.203059-04:00" }

      Мы определили структурный тег, описывающий пользователя с помощью полей, включая имя, пароль и время создания пользователя. В функции main мы создаем экземпляр этого пользователя, предоставляя значения для всех полей, кроме PreferredFish (Sammy нравятся все рыбы). Затем мы передаем экземпляр User в функцию json.MarshalIndent. Это позволяет нам просматривать результаты выполнения JSON в удобном виде без внешнего инструмента форматирования. Этот вызов можно заменить на json.Marshal(u) для получения JSON без дополнительных пробелов. Два дополнительных аргумента json.MarshalIndent определяют префикс результатов (который мы пропустили при выводе пустой строки) и символы отступа, в данном случае — два символа пробела. Любые ошибки json.MarshalIndent регистрируются, и программа завершает работу с помощью os.Exit(1). Наконец, мы кастуем []byte, возвращаемый json.MarshalIndent, в string, а затем передаем эту строку в функцию fmt.Println для печати на терминале.

      Поля структуры соответствуют присвоенным им именам. Это не обычный стиль JSON, где используются названия полей, где первая буква каждого слова, кроме первого, — заглавная («верблюжий стиль»). Вы можете изменить имена полей в соответствии с «верблюжьим стилем», как показано в следующем примере. Как видно при выполнении этого образца, это не сработает, поскольку желаемые имена полей противоречат правилам Go в отношении имен экспортируемых полей.

      package main
      
      import (
          "encoding/json"
          "fmt"
          "log"
          "os"
          "time"
      )
      
      type User struct {
          name          string
          password      string
          preferredFish []string
          createdAt     time.Time
      }
      
      func main() {
          u := &User{
              name:      "Sammy the Shark",
              password:  "fisharegreat",
              createdAt: time.Now(),
          }
      
          out, err := json.MarshalIndent(u, "", "  ")
          if err != nil {
              log.Println(err)
              os.Exit(1)
          }
      
          fmt.Println(string(out))
      }
      

      В результате выводится следующее:

      Output

      {}

      В этой версии мы изменили имена полей в соответствии с «верблюжьим стилем». Теперь Name соответствует name, Password соответствует password, а CreatedAt соответствует createdAt. В теле main мы изменили инициациализацию структуры для использования новых имен. Затем мы передаем структуру в функцию json.MarshalIndent, как и ранее. Сейчас в результате выводится пустой объект JSON, {}.

      Для правильного отображения полей в «верблюжьем стиле» требуется, чтобы первый символ был в нижнем регистре. Хотя JSON не важны имена полей, для Go они имеют значение, поскольку от этого зависит видимость полей вне пакета. Поскольку пакет encoding/json является отдельным пакетом от используемого нами пакета main, первый символ его имени должен быть в верхнем регистре, чтобы он был видимым для encoding/json. Похоже мы в безвыходном положении, и нам нужен способ передать кодировщику JSON желаемое имя этого поля.

      Использование структурных тегов для управления кодировкой

      Вы можете изменить предыдущий пример так, чтобы экспортируемые поля правильно кодировались с именами полей в «верблюжьем стиле». Для этого мы аннотируем каждое поле структурным тегом. Структурный тег, распознаваемый encoding/json, имеет ключ json и значение, определяющее выводимый результат. Если мы поместим имена полей в «верблюжьем стиле» в качестве значения ключа json, кодировщик будет использовать эти имена. В данном примере решена проблема, наблюдавшаяся в предыдущих двух попытках:

      package main
      
      import (
          "encoding/json"
          "fmt"
          "log"
          "os"
          "time"
      )
      
      type User struct {
          Name          string    `json:"name"`
          Password      string    `json:"password"`
          PreferredFish []string  `json:"preferredFish"`
          CreatedAt     time.Time `json:"createdAt"`
      }
      
      func main() {
          u := &User{
              Name:      "Sammy the Shark",
              Password:  "fisharegreat",
              CreatedAt: time.Now(),
          }
      
          out, err := json.MarshalIndent(u, "", "  ")
          if err != nil {
              log.Println(err)
              os.Exit(1)
          }
      
          fmt.Println(string(out))
      }
      

      Результат будет выглядеть так:

      Output

      { "name": "Sammy the Shark", "password": "fisharegreat", "preferredFish": null, "createdAt": "2019-09-23T18:16:17.57739-04:00" }

      Мы снова изменили имена полей так, чтобы сделать их видимыми для других пакетов. Для этого мы сделали заглавными первые буквы имен этих полей. Однако в этот раз мы добавили структурные теги в форме json:"name", где "name" — имя, которое json.MarshalIndent должен использовать при печати нашей структуры в формате JSON.

      Мы успешно и правильно отформатировали код JSON. Однако следует отметить, что поля для некоторых значений были распечатаны, хотя мы и не задавали эти значения. Если вы захотите, кодировщик JSON может ликвидировать эти поля.

      Удаление пустых полей JSON

      Чаще всего мы не хотим выводить поля, которые не заданы в JSON. Поскольку все типы Go имеют заданное по умолчанию «нулевое значение», пакету encoding/json требуется дополнительная информация, чтобы он считал поле не заданным, если оно имеет это нулевое значение. В части значения любого структурного тега json вы можете задать суффикс желаемого имени поля с опцией ,omitempty, чтобы кодировщик JSON не выводил это поле, если для него задано нулевое значение. В следующем примере мы устранили заметную в предыдущих примерах проблему вывода пустых полей:

      package main
      
      import (
          "encoding/json"
          "fmt"
          "log"
          "os"
          "time"
      )
      
      type User struct {
          Name          string    `json:"name"`
          Password      string    `json:"password"`
          PreferredFish []string  `json:"preferredFish,omitempty"`
          CreatedAt     time.Time `json:"createdAt"`
      }
      
      func main() {
          u := &User{
              Name:      "Sammy the Shark",
              Password:  "fisharegreat",
              CreatedAt: time.Now(),
          }
      
          out, err := json.MarshalIndent(u, "", "  ")
          if err != nil {
              log.Println(err)
              os.Exit(1)
          }
      
          fmt.Println(string(out))
      }
      

      Результат выполнения будет выглядеть так:

      Output

      { "name": "Sammy the Shark", "password": "fisharegreat", "createdAt": "2019-09-23T18:21:53.863846-04:00" }

      Мы изменили предыдущие примеры так, что теперь поле PreferredFish имеет структурный тег json:"preferredFish,omitempty". Благодаря опции ,omitempty кодировщик JSON пропускает это поле, поскольку мы не задаем его. В предыдущих примерах результатом было значение null.

      Теперь результаты выглядят намного лучше, однако мы по прежнему распечатываем пароль пользователя. Пакет encoding/json дает нам еще один способ полностью игнорировать конфиденциальные поля.

      Игнорирование конфиденциальных полей

      Некоторые поля необходимо экспортировать из структур, чтобы другие пакеты могли правильно взаимодействовать с типом. Однако эти поля могут носить конфиденциальный характер, и в данном случае мы хотим, чтобы кодировщик JSON полностью игнорировал поле—даже если оно задано. Для этого используется специальное значение - в качестве аргумента для структурного тега json:.

      В этом примере мы исправили проблему раскрытия пароля пользователя.

      package main
      
      import (
          "encoding/json"
          "fmt"
          "log"
          "os"
          "time"
      )
      
      type User struct {
          Name      string    `json:"name"`
          Password  string    `json:"-"`
          CreatedAt time.Time `json:"createdAt"`
      }
      
      func main() {
          u := &User{
              Name:      "Sammy the Shark",
              Password:  "fisharegreat",
              CreatedAt: time.Now(),
          }
      
          out, err := json.MarshalIndent(u, "", "  ")
          if err != nil {
              log.Println(err)
              os.Exit(1)
          }
      
          fmt.Println(string(out))
      }
      

      При запуске этого примера вы увидите следующие результаты:

      Output

      { "name": "Sammy the Shark", "createdAt": "2019-09-23T16:08:21.124481-04:00" }

      Единственное изменение в этом примере по сравнению с предыдущими заключается в том, что в поле пароля теперь используется специальное значение "-" для структурного тега json:. В результатах в этом примере мы видим, что поле password больше не отображается.

      Эти возможности пакета encoding/json, ,omitempty и "-" не являются стандартными. Действия пакета со значениями структурного тега зависят от реализации. Поскольку пакет encoding/json является частью стандартной библиотеки, в других пакетах эти возможности также реализованы стандартным образом. Однако важно ознакомиться с документацией по любым сторонним пакетам, использующим теги структуры, чтобы узнать, что поддерживается, а что нет.

      Заключение

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



      Source link