На этом шаге мы начнем рассматривать пример создания и реализации пользовательского типа с единственным значением.
Рассмотрим пример реализации для представления логических значений с использованием вещественных чисел: 0.0 – для значения false и 1.0 – для значения true, 0.5 означает 50% истины (50% лжи), значение 0.25 означает 25% истины (75% лжи) и т. д.
Разрабатываемый тип называется NLogic. Сначала рассмотрим определение самого типа:
//Тип NLogic основан на структуре, содержащей единственное поле типа float32 type NLogic struct{ val float32 }
Поле val является неэкспортируемым, поэтому для создания значений типа NLogic в пакетах, импортирующих пакет nlogic, необходимо использовать функцию-конструктор New() в соответствии с соглашениями, принятыми в языке Go, тем самым мы можем гарантировать создание допустимых значений типа NLogic.
func New(val interface{}) (*NLogic, error) { v, err := float32ForValue(val) return &NLogic{v}, err }
Функция New() возвращает указатель на значение типа NLogic. Это означает, что методам, изменяющим значение типа NLogic (метод Set()), приемник должен передаваться по указателю, а не по значению. Указатели лучше подходят для работы с большими составными типами (например, с двумя и более полями), чтобы их значения можно было передавать в виде одного простого указателя.
При работе с типом NLogic инициализировать новые значения этого типа можно не только значениями типа float32, но и значениями типа float64, int и bool. Это достигается за счет использования собственной функции float32ForValue(), возвращающей значение типа float32 и nil для указанного значения, или 0.0 и значение типа error, если функции было передано значение неподдерживаемого типа.
func float32ForValue(val interface{}) (log float32, err error) { switch val := val.(type) { case float32: log = val case float64: log = float32(val) case int: log = float32(val) case bool: log = 0 if val { log = 1 } default: return 0, fmt.Errorf("float32ForValue(): %v не является "+ "числом или логическим значением", val) } if log < 0 { log = 0 } else if log > 1 { log = 1 } return log, nil }
Эта неэкспортируемая вспомогательная функция используется методом New() для преобразования значения val в значение типа float32 в диапазоне [0.0, 1.0]. Обработка исходных значений различных типов реализуется с помощью инструкции switch выбора по типу.
Если функции передается значение недопустимого типа, она возвращает непустое значение типа error. Это позволяет вызывающей программе проверить возвращаемое значение и предпринять необходимые действия в случае появления ошибки. Вызывающая программа может возбудить аварийную ситуацию и вызвать крах приложения с выводом трассировочной информации или как-то иначе решить проблему. В низкоуровневых функциях, подобных этой, часто лучше просто возвращать признак ошибки, чтобы сообщить о проблеме, потому что они не обладают достаточной информацией о логике работы приложения и о том, как следует обрабатывать ошибку, тогда как вызывающая программа находится в лучшем положении и знает, какие действия следует предпринять.
Передача значения недопустимого типа является программной ошибкой, следовательно, нужно возвращать непустое значение типа error, для значений допустимых типов вне допустимого диапазона производить преобразование в ближайшее допустимое значение.
На следующем шаге продолжим рассматривать пример создания пользовательского типа с единственным значением.
Функцию-конструктор nlogic.New() можно рассматривать как функцию преобразования типа, поскольку она может принимать значения типов float32, float64, int и bool, и возвращает значение типа *NLogic. Два метода, приведенные ниже, выполняют обратные преобразования.
func (log *NLogic) Bool() bool { return log.val >= .5 } func (log *NLogic) Float() float64 { return float64(log.val) }