Ответ 1
Дефекты
Предложенное вами решение небезопасно так, как вы хотите. Можно использовать нетипизированные целочисленные константы для создания новых значений unary
имеющих значение int
отличное от 1
или -1
. Смотрите этот пример:
p := unary.Positive
fmt.Printf("%v %d\n", p, p)
p = 3
fmt.Printf("%v %d\n", p, p)
Выход будет:
+ 1
- 3
Мы можем изменить значение p
чтобы сохранить значение int
3
которое, очевидно, не равно Positive
или Negative
. Это возможно, потому что Spec: Assignability:
Значение
x
присваивается переменной типаT
("x
присваиваетсяT
") в любом из следующих случаев:
- ...
x
- нетипизированная константа, представимая значением типаT
3
- нетипизированная константа, и она может быть представлена значением типа unary
типа, который имеет базовый тип int
.
В Go вы не можете иметь "безопасные" константы, для которых "посторонние" пакеты не могут создавать новые значения, по вышеуказанной причине. Потому что, если вы хотите объявить константы в вашем пакете, вы можете использовать только выражения с "нетипизированными" версиями, которые также могут использоваться другими пакетами в присваиваниях (как в нашем примере).
Неэкспортированная структура
Если вы хотите выполнить "безопасную" часть, вы можете использовать неэкспортированные struct
, но тогда они не могут быть использованы в объявлениях констант.
Пример:
type unary struct {
val int
}
var (
Positive = unary{1}
Negative = unary{-1}
)
func (u unary) String() string {
if u == Positive {
return "+"
}
return "-"
}
func (u unary) CalExpr() int {
return u.val
}
Попытка изменить его значение:
p := unary.Positive
p.val = 3 // Error: p.val undefined (cannot refer to unexported field or method val)
p = unary.unary{3} // Error: cannot refer to unexported name unary.unary
// Also error: implicit assignment of unexported field 'val' in unary.unary literal
Обратите внимание, что поскольку мы сейчас используем struct
, мы можем еще больше упростить наш код, добавив string
представление наших значений в struct
:
type unary struct {
val int
str string
}
var (
Positive = unary{1, "+"}
Negative = unary{-1, "-"}
)
func (u unary) String() string { return u.str }
func (u unary) CalExpr() int { return u.val }
Обратите внимание, что у этого решения все еще есть "недостаток": оно использует экспортированные глобальные переменные, значения которых могут быть изменены другими пакетами. Это правда, что другие пакеты не могут создавать и назначать новые значения, но они могут делать это с существующими значениями, например:
unary.Positive = unary.Negative
Если вы хотите защитить себя от такого неправильного использования, вы также должны сделать такие глобальные переменные не экспортируемыми. И затем, конечно, вам нужно создать экспортированные функции для представления этих значений, например:
var (
positive = unary{1}
negative = unary{-1}
)
func Positive() unary { return positive }
func Negative() unary { return negative }
Затем получить/использовать значения:
p := unary.Positive()
Интерфейс
Необходимо соблюдать осторожность, если вы планируете использовать тип интерфейса для ваших "констант". Пример можно увидеть в ответе Каве Шахбазиана. Неизвестный метод используется для предотвращения реализации интерфейса другими, создавая иллюзию, что другие действительно не могут реализовать его:
type Unary interface {
fmt.Stringer
CalExpr() int
disabler() // implementing this interface outside this package is disabled
}
var (
Positive Unary = unary(1) // visible outside of the package unary
Negative Unary = unary(-1) // visible outside of the package unary
)
type unary int // not visible outside of the package unary
func (u unary) disabler() {}
func (u unary) String() string { /* ... */ }
func (u unary) CalExpr() int { /* ... */ }
Это не тот случай, однако. С помощью подвоха это можно обойти. Экспортированный Unary
тип может быть встроен, и существующее значение может использоваться для реализации интерфейса (вместе с неэкспортированным методом), и мы можем добавить наши собственные реализации экспортированных методов, делая/возвращая все, что мы хотим.
Вот как это может выглядеть:
type MyUn struct {
unary.Unary
}
func (m MyUn) String() string { return "/" }
func (m MyUn) CalExpr() int { return 3 }
Тестирование это:
p := unary.Positive
fmt.Printf("%v %d\n", p, p)
p = MyUn{p}
fmt.Printf("%v %d\n", p, p.CalExpr())
Выход:
+ 1
/ 3
Особый случай
Как отметил Фолькер в своем комментарии, в вашем особом случае вы могли бы просто использовать
type unary bool
const (
Positive unary = true
Negative unary = false
)
Поскольку тип bool
имеет два возможных значения: true
и false
, и мы использовали все. Таким образом, нет других значений, которые можно было бы "использовать" для создания других значений нашего константного типа.
Но знайте, что это можно использовать, только если число констант равно количеству возможных значений типа, поэтому удобство использования этого метода очень ограничено.
Также имейте в виду, что это не предотвращает такие злоупотребления, когда ожидается тип unary
типа, и кто-то случайно передает нетипизированную константу, такую как true
или false
.