Расскажите о паттерне «Цепочка обязанностей» (Chain of Responsibility) и приведите пример его использования в Go.

Ответ

Chain of Responsibility — это поведенческий паттерн проектирования, который позволяет передавать запросы последовательно по цепочке обработчиков. Каждый обработчик решает, может ли он обработать запрос сам, или же он передает его следующему обработчику в цепочке.

Самый частый и идиоматичный пример использования этого паттерна в Go — это HTTP middleware.

Пример: Middleware для HTTP-сервера

Каждый middleware — это звено в цепочке. Он выполняет свою логику (например, логирование или аутентификацию) и передает управление следующему обработчику.

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

// Middleware для логирования запросов
func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("Started %s %s", r.Method, r.URL.Path)
        // Передаем управление следующему обработчику в цепочке
        next.ServeHTTP(w, r)
        log.Printf("Completed in %v", time.Since(start))
    })
}

// Middleware для проверки "аутентификации"
func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Header.Get("X-API-KEY") != "secret" {
            http.Error(w, "Forbidden", http.StatusForbidden)
            return // Прерываем цепочку, если аутентификация не пройдена
        }
        // Передаем управление дальше
        next.ServeHTTP(w, r)
    })
}

// Конечный обработчик запроса
func finalHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello, World!")
}

func main() {
    final := http.HandlerFunc(finalHandler)
    // Собираем цепочку: сначала логирование, потом аутентификация
    chain := LoggingMiddleware(AuthMiddleware(final))

    http.Handle("/", chain)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Плюсы:

  • Гибкость: Позволяет динамически добавлять, удалять и изменять порядок обработчиков.
  • Разделение ответственности: Каждый обработчик сфокусирован на одной задаче (логирование, аутентификация, кэширование и т.д.).
  • Слабая связность: Обработчик не знает о структуре цепочки или о других обработчиках, он знает только о следующем звене.

Минусы:

  • Нет гарантии обработки: Запрос может пройти всю цепочку и остаться необработанным.
  • Сложность отладки: Может быть трудно отследить, какой именно обработчик в цепочке вызвал ошибку или повел себя некорректно.