Menu
Coddy logo textTech

Интерфейс как контракт

Часть раздела Объектно-ориентированное программирование путешествия по 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 выполнен.

challenge icon

Задание

Легко

Давайте создадим систему уведомлений, которая демонстрирует, как интерфейсы выступают в роли contract. Вы создадите интерфейс Notifier, который определяет, что значит отправить notification, а затем Implement несколько каналов уведомлений, каждый из которых соблюдает этот contract.

Вы организуете код в двух файлах:

  • notifiers.go: Define интерфейс Notifier с единственным method Notify(message string) string. Затем создайте три разные структуры, соответствующие этому contract:
    • ConsoleNotifier с полем Prefix
    • FileNotifier с полем Filename
    • WebhookNotifier с полем URL
    Каждая структура должна Implement method 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")
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Go