Проблематика
                                              В этой статье мы рассмотрим, как работать с категоризируемыми данными. Для                         примера возьмем книжный каталог.                     
                                              Для начала мы определим структуру Book, которую планируем                         классифицировать по жанру Genre:                     
                                               package books  type Book struct {     ID    int    `json:"id"`     Name  string `json:"name"`     Genre string `json:"genre"` }                                              
                     Теперь, когда мы определились с книгой, давайте определим константы                         для жанра:
                      const ( 	Adventure     = "Adventure" 	Comic         = "Comic" 	Crime         = "Crime" 	Fiction       = "Fiction" 	Fantasy       = "Fantasy" 	Historical    = "Historical" 	Horror        = "Horror" 	Magic         = "Magic" 	Mystery       = "Mystery" 	Philosophical = "Philosophical" 	Political     = "Political" 	Romance       = "Romance" 	Science       = "Science" 	Superhero     = "Superhero" 	Thriller      = "Thriller" 	Western       = "Western" )                         
                      Пока все выглядит нормально. Однако константы жанра (Genre) являются                         строками. Хотя это                         очень «очеловеченный» способ чтения кода, он не очень эффективен для компьютерной программы.                         Строки будут занимать больше места в памяти программы (не говоря уже о том, что                         если бы мы                         хранили миллионы записей в базе данных). Поэтому мы хотим использовать более эффективный                             тип                             данных для нашего контекста.
                      В Go одним из способов сделать это является создание констант,                         основанных на типе                         int:
                      const ( 	Adventure     = 1 	Comic         = 2 	Crime         = 3 	Fiction       = 4 	Fantasy       = 5 	Historical    = 6 	Horror        = 7 	Magic         = 8 	Mystery       = 9 	Philosophical = 10 	Political     = 11 	Romance       = 12 	Science       = 13 	Superhero     = 14 	Thriller      = 15 	Western       = 16 )                         
                     Нам также нужно изменить структуру Book, чтобы теперь                         Genre имел тип int:
                       type Book struct { 	ID    int 	Name  string 	Genre int // Заменили тип string на int для увеличения производительности }                         
                      Хотя теперь у нас есть более эффективная модель памяти для Genre, она                         не так удобна                         для человека. Если я выведу значение Book, то теперь мы получим просто целочисленное                             значение.                         Чтобы показать это, мы напишем быстрый тест:
                       package books  import "testing"  func TestGenre(t *testing.T) { 	b := Book{ 		ID:    1, 		Name:  "Всё про Golang", 		Genre: Magic, 	}  	t.Logf("%+v\n", b)  	if got, exp := b.Genre, 8; got != exp { 		t.Errorf("unexpected genre.  got %d, exp %d", got, exp) 	} }                         
                      И вот что он выведет после запуска:
                      $go test -v ./...  === RUN   TestGenre     book_test.go:12: {ID:1 Name:Всё про Golang Genre:8} --- PASS: TestGenre (0.00s)  PASS ok      bitbucket.org/ampleevee/examples.git/internal/books      0.273s   -------- Go Version: go 1.22.0                         
                      Заметьте, что Genre просто показывает значение 8. Каждый                         раз, когда мы отлаживаем                         код, пишем отчет и т. д., нам нужно выяснить, что на самом деле означает 8 для                         человека.
                      Для этого мы можем написать вспомогательную функцию, которая принимает значение                         Genre и определяет, каким должно быть «человеческое» представление:
                       func GenreToString(i int) string { 	switch i { 	case 1: 		return "Adventure" 	case 2: 		return "Comic" 	case 3: 		return "Crime" 	case 4: 		return "Fiction" 	case 5: 		return "Fantasy" 	case 6: 		return "Historical" 	case 7: 		return "Horror" 	case 8: 		return "Magic" 	case 9: 		return "Mystery" 	case 10: 		return "Philosophical" 	case 11: 		return "Political" 	case 12: 		return "Romance" 	case 13: 		return "Science" 	case 14: 		return "Superhero" 	case 15: 		return "Thriller" 	case 16: 		return "Western" 	default: 		return "" 	} }                         
                     Более эффективный способ
                     Хотя весь приведенный выше код работает отлично, в нем не хватает некоторых ключевых                         моментов:
                                               - Если в будущем значение Genreизменится, нам придется не только изменить                             константное                             значение, но и обновить функциюGenreToString. Если этого не сделать, то в коде                             возникнет                             ошибка.
- Мы не используем систему типов, чтобы инкапсулировать это поведение для Genre.                             Скоро мы покажем вам, что мы имеем в виду.
                         Первое, что нам нужно сделать, это написать более устойчивую                         функцию GenreToString.                         Под устойчивостью мы понимаем то, что даже если значение константы                         Genre изменится в будущем,                         функция GenreToString не должна будет меняться.                     
                     Правильный способ сделать это - не использовать жестко                             закодированные значения, а                         использовать значения самих констант:
                      func GenreToString(i int) string { switch i { 	case Adventure: 		return "Adventure" 	case Comic: 		return "Comic" 	case Crime: 		return "Crime" 	case Fiction: 		return "Fiction" 	case Fantasy: 		return "Fantasy" 	case Historical: 		return "Historical" 	case Horror: 		return "Horror" 	case Magic: 		return "Magic" 	case Mystery: 		return "Mystery" 	case Philosophical: 		return "Philosophical" 	case Political: 		return "Political" 	case Romance: 		return "Romance" 	case Science: 		return "Science" 	case Superhero: 		return "Superhero" 	case Thriller: 		return "Thriller" 	case Western: 		return "Western" 	default: 		return "" 	} }
                     Хорошо, это гораздо чище (и читабельнее), но мы все еще не решили проблему того, что                         при выводе на печать отображается значение данных (int), а не «человеческое»                         читаемое                         значение.
                     Собственные типы Go нам в помощь
                     Вместо того чтобы использовать стандартный тип int для                         Genre, мы можем создать свой                         собственный тип на основе стандартного. В данном случае мы создадим новый тип                         Genre на                         основе типа int:
                      type Genre int  type Book struct { 	ID    int 	Name  string 	Genre Genre // Заменили тип int на собственный Genre без потери производительности }
                      Теперь мы определим наши константы как типы Genre:
                      const ( 	Adventure     Genre = 1 	Comic         Genre = 2 	Crime         Genre = 3 	Fiction       Genre = 4 	Fantasy       Genre = 5 	Historical    Genre = 6 	Horror        Genre = 7 	Magic         Genre = 8 	Mystery       Genre = 9 	Philosophical Genre = 10 	Political     Genre = 11 	Romance       Genre = 12 	Science       Genre = 13 	Superhero     Genre = 14 	Thriller      Genre = 15 	Western       Genre = 16 ) 
                      Пока что в коде нет никаких изменений. Однако теперь, когда Genre - это                         собственный                             тип, мы можем добавить к нему методы. Это позволит инкапсулировать                             «человеческое» поведение,                         которое мы хотим получить, в тип, а не в общую функцию.
                      Для этого мы добавим метод String к типу                         Genre:
                      func (g Genre) String() string { 	switch g { 	case Adventure: 		return "Adventure" 	case Comic: 		return "Comic" 	case Crime: 		return "Crime" 	case Fiction: 		return "Fiction" 	case Fantasy: 		return "Fantasy" 	case Historical: 		return "Historical" 	case Horror: 		return "Horror" 	case Magic: 		return "Magic" 	case Mystery: 		return "Mystery" 	case Philosophical: 		return "Philosophical" 	case Political: 		return "Political" 	case Romance: 		return "Romance" 	case Science: 		return "Science" 	case Superhero: 		return "Superhero" 	case Thriller: 		return "Thriller" 	case Western: 		return "Western" 	default: 		return "" 	} }
                                               Теперь мы сможем использовать метод String, когда захотим узнать, каково                         «человеческое» значение у Genre (данный код уже писался в другом пакете, поэтому                         здесь отдельно импортируется пакет books):                     
                       package main  import ( 	"bitbucket.org/ampleevee/examples.git/internal/books" 	"fmt" )  func main() { 	b := books.Book{ 		ID:    1, 		Name:  "Всё про Go", 		Genre: books.Magic, 	} 	fmt.Println(b.Genre.String()) }                         
                      Вывод:
                     Magic
                     Магическое форматирование
                     В Go, если вы добавите метод String к любому типу, пакет                         fmt будет                         использовать его при выводе вашего типа автоматически. Благодаря этому                         мы увидим, что если мы распечатаем Book в наших тестах, то получим и                         «человекочитаемый»                         Genre:
                      $go test -v ./...  === RUN   TestGenre     book_test.go:12: {ID:1 Name:Всё про Golang Genre:Magic} --- PASS: TestGenre (0.00s)  PASS ok      bitbucket.org/ampleevee/examples.git/internal/books      0.273s   -------- Go Version: go 1.22.0
                      Теперь мы видим, что в распечатанном выводе значение для Genre - Magic,                         а не 8.                         Важно также отметить, что наш тест фактически не изменился, изменился только                         способ, которым мы                         использовали наш новый тип для Genre.
                      А как же iota?
                     Те из вас, кто уже знаком с Go, возможно, посмотрели на эту задачу                         и спросили:                         «Почему вы просто не использовали iota?». iota - это идентификатор,                         который вы можете                         использовать в Go для создания увеличивающихся числовых                             констант. Хотя есть несколько причин, по                         которым я не использовал iota в этой задаче, я посвятил этой теме целую статью.                         Читайте об этом                         в статье «Где и когда использовать iota                             в Golang?».
                      Резюме
                     Хотя этот пример был намеренно базовым по своей природе, он                         иллюстрирует возможности                         определения собственных типов и использования системы типов в Go для создания                         более стойкого,                         читаемого и многократно используемого кода.