Интерфейсы Stringer и Error
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 32 из 107.
Стандартная библиотека Go определяет два интерфейса, которые вы будете использовать постоянно: fmt.Stringer и error. Оба являются простыми интерфейсами с одним методом, которые обеспечивают вашим типам мощную интеграцию со встроенными возможностями Go.
Интерфейс Stringer находится в пакете fmt и требует всего один метод:
type Stringer interface {
String() string
}Когда вы реализуете String() для вашего типа, такие функции, как fmt.Println, автоматически используют его для отображения вашего значения:
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s (%d years old)", p.Name, p.Age)
}
func main() {
p := Person{Name: "Alice", Age: 30}
fmt.Println(p) // Alice (30 лет)
}Интерфейс error так же прост и требует наличия метода Error(), который возвращает строку:
type error interface {
Error() string
}Любой тип с методом Error() может использоваться в качестве значения ошибки в Go. Это позволяет создавать пользовательские типы ошибок с дополнительным контекстом:
type ValidationError struct {
Field string
Message string
}
func (e ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
func main() {
err := ValidationError{Field: "email", Message: "invalid format"}
fmt.Println(err) // email: invalid format
}Эти два интерфейса демонстрируют философию Go: небольшие, узкоспециализированные интерфейсы, которые обеспечивают мощную функциональность благодаря неявной реализации.
Задание
ЛегкоДавайте создадим систему мониторинга температуры, которая использует интерфейсы Stringer и error для обеспечения читаемого вывода и осмысленной обработки ошибок.
Вы организуете свой код в двух файлах:
temperature.go: Создайте структуруTemperatureс полямиValue(float64) иUnit(string). Реализуйте методString()так, чтобы при выводе на печать он отображал температуру в удобном для человека формате. Также создайте структуруTemperatureErrorс полямиTemp(float64) иReason(string) и реализуйте методError()для предоставления описательных сообщений об ошибках.main.go: Создайте функцию с именемValidateTemperature, которая принимаетTemperatureи возвращает ошибку, если температура недействительна. Температура считается недействительной, если она ниже абсолютного нуля (-273.15 для Celsius, -459.67 для Fahrenheit). Если температура валидна, функция возвращаетnil. Считайте данные о температуре из входного потока, создайте Temperature, проверьте её и выведите соответствующий результат.
Будут предоставлены следующие входные данные:
- Строка 1: Значение температуры (float)
- Строка 2: Единица измерения (
Cдля Celsius илиFдля Fahrenheit)
Ваш метод String() должен возвращать формат:
[Value] degrees [Unit]Где Unit — это полное слово "Celsius" или "Fahrenheit" в зависимости от входных данных.
Ваш метод Error() должен возвращать формат:
invalid temperature [Temp]: [Reason]Если температура валидна, выведите Temperature с помощью fmt.Println (который будет использовать ваш метод String()). Если невалидна, выведите ошибку (которая будет использовать ваш метод Error()).
Например, для 25.5 и C ваш вывод должен быть:
25.5 degrees CelsiusДля -500 и F ваш вывод должен быть:
invalid temperature -500: below absolute zeroИспользуйте "below absolute zero" в качестве причины (reason), когда температура слишком низкая.
Шпаргалка
Стандартная библиотека Go определяет два важных интерфейса: fmt.Stringer и error.
Интерфейс Stringer
Интерфейс Stringer из пакета fmt требует наличия одного метода:
type Stringer interface {
String() string
}Когда вы реализуете метод String() для своего типа, такие функции, как fmt.Println, автоматически используют его:
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s (%d years old)", p.Name, p.Age)
}
func main() {
p := Person{Name: "Alice", Age: 30}
fmt.Println(p) // Alice (30 years old)
}Интерфейс error
Интерфейс error требует наличия метода Error(), который возвращает строку:
type error interface {
Error() string
}Любой тип с методом Error() может использоваться в качестве значения ошибки, что позволяет создавать пользовательские типы ошибок с дополнительным контекстом:
type ValidationError struct {
Field string
Message string
}
func (e ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
func main() {
err := ValidationError{Field: "email", Message: "invalid format"}
fmt.Println(err) // email: invalid format
}Попробуйте сами
package main
import (
"fmt"
)
// ValidateTemperature проверяет, является ли температура допустимой
// Возвращает ошибку, если температура ниже абсолютного нуля
// Абсолютный нуль: -273.15 для Celsius, -459.67 для Fahrenheit
func ValidateTemperature(t Temperature) error {
// TODO: Реализовать логику валидации
// Проверить, ниже ли температура абсолютного нуля в зависимости от единицы измерения
// Вернуть TemperatureError, если значение невалидно, и nil, если валидно
return nil
}
func main() {
var value float64
var unit string
fmt.Scanln(&value)
fmt.Scanln(&unit)
// TODO: Создать структуру Temperature с входными значениями
// TODO: Проверить температуру с помощью ValidateTemperature
// TODO: Если возникла ошибка, вывести её
// В противном случае вывести температуру (будет использован метод String())
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool