Как идиоматично представлять перечисления (enum) в Go?
Я пытаюсь представить упрощенную хромосому, которая состоит из N оснований, каждое из которых может быть только одним из {A, C, T, G}.
Я хотел бы формализовать ограничения с помощью перечисления (enum), но меня интересует, какой самый идиоматичный способ эмуляции перечислений в языке Go.
5 ответ(ов)
Ссылаясь на ответ jnml, вы можете предотвратить создание новых экземпляров типа Base, если не экспортировать его вовсе (то есть написать его с маленькой буквы). При необходимости вы можете создать экспортируемый интерфейс, который будет иметь метод, возвращающий тип base. Этот интерфейс можно использовать в функциях снаружи, которые работают с Base, например:
package a
type base int
const (
A base = iota
C
T
G
)
type Baser interface {
Base() base
}
// каждый base должен реализовывать интерфейс Baser
func (b base) Base() base {
return b
}
func (b base) OtherMethod() {
}
package main
import "a"
// функция снаружи, которая работает с a.base через a.Baser
// поскольку a.base не экспортирован, можно использовать только экспортируемые базы, созданные внутри пакета a, такие как a.A, a.C, a.T и a.G
func HandleBasers(b a.Baser) {
base := b.Base()
base.OtherMethod()
}
// функция снаружи, которая возвращает a.A или a.C в зависимости от условия
func AorC(condition bool) a.Baser {
if condition {
return a.A
}
return a.C
}
Внутри пакета main интерфейс a.Baser фактически работает как перечисление (enum). Только внутри пакета a вы можете определять новые экземпляры.
Вы можете сделать это следующим образом:
type MessageType int32
const (
TEXT MessageType = 0
BINARY MessageType = 1
)
С помощью этого кода компилятор будет проверять тип перечисления (enum). Чтобы обеспечить типобезопасность, можно добавить метод для вашего типа MessageType, который будет проверять корректность присваиваемого значения. Однако, данный код уже предоставляет некоторую степень проверки благодаря определению типа MessageType, поэтому вы не сможете случайно присвоить значение другого типа.
Действительно, приведённые выше примеры использования const и iota — это наиболее идиоматичные способы представления примитивных перечислений в Go. Но если вы хотите создать более функциональное перечисление, похожее на тип, который вы могли бы увидеть в других языках, таких как Java или Python, вы можете воспользоваться следующим подходом.
Очень простой способ создать объект, который начинает походить на строковое перечисление в Python, выглядит так:
package main
import (
"fmt"
)
var Colors = newColorRegistry()
func newColorRegistry() *colorRegistry {
return &colorRegistry{
Red: "red",
Green: "green",
Blue: "blue",
}
}
type colorRegistry struct {
Red string
Green string
Blue string
}
func main() {
fmt.Println(Colors.Red)
}
Предположим, что вам также нужны некоторые вспомогательные методы, такие как Colors.List() и Colors.Parse("red"). Если ваши цвета более сложные и должны быть структурой, вы можете сделать что-то вроде этого:
package main
import (
"errors"
"fmt"
)
var Colors = newColorRegistry()
type Color struct {
StringRepresentation string
Hex string
}
func (c *Color) String() string {
return c.StringRepresentation
}
func newColorRegistry() *colorRegistry {
red := &Color{"red", "F00"}
green := &Color{"green", "0F0"}
blue := &Color{"blue", "00F"}
return &colorRegistry{
Red: red,
Green: green,
Blue: blue,
colors: []*Color{red, green, blue},
}
}
type colorRegistry struct {
Red *Color
Green *Color
Blue *Color
colors []*Color
}
func (c *colorRegistry) List() []*Color {
return c.colors
}
func (c *colorRegistry) Parse(s string) (*Color, error) {
for _, color := range c.List() {
if color.String() == s {
return color, nil
}
}
return nil, errors.New("не удалось найти")
}
func main() {
fmt.Printf("%s\n", Colors.List())
}
На этом этапе, конечно, это работает, но вам может не нравиться, что вы должны постоянно определять цвета. Если на этом этапе вы хотите избавиться от этого, вы можете использовать теги в вашей структуре и применить рефлексию для настройки, но надеюсь, что этот пример охватывает большинство случаев.
В Go есть способ организовать перечисления с помощью структуры (struct) и объединения всех значений в специфическом пространстве имен. Преимущество этого подхода заключается в том, что все переменные перечисления находятся под определенным пространством имен, что помогает избежать загрязнения глобальной области видимости.
Однако стоит отметить, что в этом случае мы можем использовать только var, а не const. Это приводит к некоторым ограничениям, так как var переменные обладают меньшей производительностью по сравнению с const.
Вот пример того, как это может быть реализовано:
type OrderStatusType string
var OrderStatus = struct {
APPROVED OrderStatusType
APPROVAL_PENDING OrderStatusType
REJECTED OrderStatusType
REVISION_PENDING OrderStatusType
}{
APPROVED: "approved",
APPROVAL_PENDING: "approval pending",
REJECTED: "rejected",
REVISION_PENDING: "revision pending",
}
В этом примере все значения статуса заказа находятся в OrderStatus, что позволяет использовать их в коде, не беспокоясь о конфликте имен.
Для данного случая может быть полезно использовать строковую константу, чтобы ее можно было сериализовать в JSON-строку. В следующем примере []Base{A, C, G, T} будет сериализовано в ["adenine", "cytosine", "guanine", "thymine"].
type Base string
const (
A Base = "adenine"
C = "cytosine"
G = "guanine"
T = "thymine"
)
При использовании iota значения будут сериализованы в целые числа. В следующем примере []Base{A, C, G, T} будет сериализовано в [0, 1, 2, 3].
type Base int
const (
A Base = iota
C
G
T
)
Вот пример, сравнивающий оба подхода:
Сравнение членов enum в Java: использовать == или equals()?
Как получить имена элементов перечисления (enum)?
Отформатировать строку в Go без вывода?
Получение тега поля структуры с использованием пакета reflect в Go
Golang - как отсортировать строку или []byte?