Уроки модуля (4/5)
Интерфейс Стрингера
fmt.Stringer, вероятно, является наиболее часто используемым стандартным интерфейсом во всех
Стандартная библиотека Go. Это идеальная демонстрация силы
небольших интерфейсов.
Определение
type Stringer interface {
String() string
}Одна подпись. Любой тип, определяющий String() string, удовлетворяет
это. И fmt распознает этот интерфейс: когда вы передаете значение
fmt.Print*, если он реализует Stringer, результат String()
используется вместо представления по умолчанию.
Пример
type Point struct{ X, Y int }
func (p Point) String() string {
return fmt.Sprintf("(%d,%d)", p.X, p.Y)
}
fmt.Println(Point{1, 2}) // (1,2)
fmt.Printf("%v %s\n", Point{3, 4}, Point{5, 6})
// (3,4) (5,6)То же значение печатается с пользовательским форматированием в %v, %s, Println, Println,
Sprint, log и т. д. И все это путем определения одного метода.
Когда это имеет смысл
- Типы, моделирующие домен (идентификаторы, коды, координаты, перечисления).
- Ошибки с удобочитаемым представлением (см. следующий урок по
error). - Типы, которые вы часто регистрируете и для которых вам нужен фиксированный формат.
Не для всего: если тип простой и представление по умолчанию все в порядке, не добавляйте шума.
Типы перечислений: общий шаблон с iota
type Status int
const (
OK Status = iota
WARN
ERR
)
func (s Status) String() string {
switch s {
case OK: return "OK"
case WARN: return "WARN"
case ERR: return "ERR"
default: return "UNKNOWN"
}
}
fmt.Println(OK, WARN, ERR) // OK WARN ERRБез String() fmt.Println(OK) напечатал бы 0. С String(),
он печатает символическое имя. stringer
инструмент автоматически генерирует этот шаблон.
Остерегайтесь бесконечной рекурсии
type T int
func (t T) String() string {
return fmt.Sprintf("%d", int(t)) // OK: cast to int breaks the recursion
}Стрингер на приемнике указателя
Если String() имеет приемник указателя, только *T удовлетворяет Stringer.
Используя fmt.Println(t) (значение), Go просматривает набор методов T,
не находит его и использует формат по умолчанию. Чтобы этого избежать, значение
получатель обычно используется для String() (это «представление»; он не
изменить что-либо).
Попробуйте это
Реализуйте String() для Point, чтобы он возвращал (X,Y).
Показать подсказку
Используйте fmt.Sprintf для форматирования полей.
Решение доступно после 3 попыток
Реализуйте String() для типа Status (int): 0 → «ОК», 1 → «WARN», в противном случае → «ERR».
Показать подсказку
Включите значение получателя; не забудьте указать значение по умолчанию.
Решение доступно после 3 попыток
Какая подпись должна быть у метода, чтобы удовлетворить fmt.Stringer?
func (x T) ??? {}Резюме
fmt.Stringer = interface { String() string }.- Реализация меняет способ представления типа
fmt.Print*. - Идиоматика для перечислений (
iota), типов доменов, кодов/идентификаторов. - Никогда не вызывайте
fmt.Sprintf("%s", t)наtвнутриString()(рекурсия). - Соглашение: приемник значения для
String()(без мутаций, позволяет избежать «сюрпризов набора методов»).