Интерфейс как контракт
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 27 из 107.
Рассматривать интерфейсы как contract помогает создавать более качественные программы на Go. Когда function принимает тип interface, это означает: «Мне не важно, какой конкретный тип вы мне передадите, если он выполняет этот contract».
Это мышление на основе контрактов позволяет писать функции, сосредоточенные на поведении, а не на конкретных типах. Рассмотрим функцию, которой необходимо обрабатывать данные из различных источников:
type DataSource interface {
FetchData() string
}
func ProcessData(src DataSource) {
data := src.FetchData()
fmt.Println("Processing:", data)
}Функция ProcessData не знает и не заботится о том, получает ли она подключение к базе данных, клиент API или средство чтения файлов. Ей требуется лишь, чтобы входные данные могли вызывать FetchData(). Подходит любой тип, соблюдающий этот contract.
type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }
type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }
func main() {
db := Database{ConnectionString: "localhost"}
api := APIClient{Endpoint: "https://api.example.com"}
ProcessData(db) // Работает!
ProcessData(api) // Тоже работает!
}Этот подход с contract также делает ваш код более тестируемым. Во время тестирования вы можете создать тип-заглушку, который удовлетворяет interface, без необходимости использовать реальные базы данных или сетевые подключения. function, которую вы тестируете, не замечает разницы, потому что contract выполнен.
Задание
ЛегкоДавайте создадим систему уведомлений, которая демонстрирует, как интерфейсы выступают в роли contract. Вы создадите интерфейс Notifier, который определяет, что значит отправить notification, а затем Implement несколько каналов уведомлений, каждый из которых соблюдает этот contract.
Вы организуете код в двух файлах:
notifiers.go: Define интерфейсNotifierс единственным methodNotify(message string) string. Затем создайте три разные структуры, соответствующие этому contract:ConsoleNotifierс полемPrefixFileNotifierс полемFilenameWebhookNotifierс полемURL
Notify, возвращающий строку с описанием того, как будет отправлено notification.main.go: Создайте function с именемBroadcastAlert, которая принимает срез значенийNotifierи строку message. Эта function должна вызыватьNotifyдля каждого notifier и выводить результаты. Считайте конфигурацию из входных данных, создайте все три типа notifier и отправьте alert через каждый из них.
Будут предоставлены следующие входные данные:
- Строка 1: префикс console
- Строка 2: Filename
- Строка 3: URL webhook
- Строка 4: message alert
Ваши method Notify должны возвращать строки в следующих форматах:
- ConsoleNotifier:
[Prefix] [message] - FileNotifier:
Writing to [Filename]: [message] - WebhookNotifier:
POST [URL]: [message]
Например, для входных данных ALERT:, log.txt, https://hooks.example.com и Server down! результат должен быть таким:
ALERT: Server down!
Writing to log.txt: Server down!
POST https://hooks.example.com: Server down!Преимущество этого дизайна заключается в том, что ваша function BroadcastAlert не знает и не заботится о конкретных типах notifier. Ей требуется лишь, чтобы каждый из них выполнял требования contract Notifier. Позже можно добавить SlackNotifier или EmailNotifier, не изменяя function broadcast.
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
)
// BroadcastAlert отправляет сообщение через все notifiers
// TODO: Реализуйте эту функцию, чтобы:
// - Принимать срез значений Notifier и строку сообщения
// - Вызывать Notify для каждого notifier и выводить результаты
func BroadcastAlert(notifiers []Notifier, message string) {
// TODO: Пройтись циклом по notifiers и вывести результат каждого уведомления
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Читать префикс консоли
scanner.Scan()
prefix := scanner.Text()
// Читать имя файла
scanner.Scan()
filename := scanner.Text()
// Читать URL вебхука
scanner.Scan()
url := scanner.Text()
// Читать сообщение оповещения
scanner.Scan()
message := scanner.Text()
// TODO: Создать экземпляры всех трёх типов notifier
// - ConsoleNotifier с префиксом
// - FileNotifier с именем файла
// - WebhookNotifier с URL
// TODO: Создайте слайс Notifier, содержащий все три notifiers
// TODO: Вызовите BroadcastAlert с notifiers и message
_ = prefix
_ = filename
_ = url
_ = message
fmt.Println("TODO: Implement the solution")
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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Потренируйтесь самостоятельно: Онлайн-компилятор Go