Как идиоматично представлять перечисления (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()?
JavaScriptSerializer - Сериализация JSON перечислений в виде строк
Как представить 'Enum' в Python?
Как эффективно объединять строки в Go
Как вывести переменные структуры в консоль?