Adapter (Адаптер)
Проблема
У вас есть существующий код с одним интерфейсом, а нужно использовать компонент с другим. Adapter оборачивает несовместимый интерфейс, делая его совместимым с ожидаемым.
В Go Adapter встречается повсеместно -- это один из самых часто используемых паттернов. Классический пример: http.HandlerFunc.
Диаграмма
Клиент ожидает: Есть:
+----------+ +-----------+
| Notifier | | SlackAPI |
+----------+ +-----------+
| Send() | | PostMsg() |
+----------+ +-----------+
Adapter:
+------------------+
| SlackAdapter |
+------------------+
| api: *SlackAPI |
| Send() --> api.PostMsg()
+------------------+
Реализация: Структурный адаптер
package notification
import "context"
// Notifier is the interface our application expects.
type Notifier interface {
Send(ctx context.Context, to, subject, body string) error
}
// --- External Slack API (third-party, we can't change it) ---
type SlackAPI struct {
token string
baseURL string
}
func NewSlackAPI(token string) *SlackAPI {
return &SlackAPI{token: token, baseURL: "https://slack.com/api"}
}
func (s *SlackAPI) PostMessage(channel, text string) error {
// External API call...
return nil
}
// --- Adapter wraps SlackAPI to satisfy Notifier ---
type slackAdapter struct {
api *SlackAPI
channel string
}
// NewSlackNotifier adapts SlackAPI to the Notifier interface.
func NewSlackNotifier(api *SlackAPI, channel string) Notifier {
return &slackAdapter{api: api, channel: channel}
}
func (a *slackAdapter) Send(_ context.Context, _, subject, body string) error {
text := subject + "\n" + body
return a.api.PostMessage(a.channel, text)
}
Реализация: Функциональный адаптер
Самый элегантный пример в Go -- http.HandlerFunc. Это тип-функция, реализующий интерфейс.
package http
// Handler is the interface.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
// HandlerFunc is a function adapter for Handler.
// Any function with the right signature becomes a Handler.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
Используем:
// Any function can be an http.Handler through the adapter
http.Handle("/health", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}))
Свой функциональный адаптер
package notification
import "context"
// NotifyFunc is a function adapter for Notifier.
type NotifyFunc func(ctx context.Context, to, subject, body string) error
// Send implements Notifier by calling the function.
func (f NotifyFunc) Send(ctx context.Context, to, subject, body string) error {
return f(ctx, to, subject, body)
}
// Usage: any matching function becomes a Notifier
var logNotifier Notifier = NotifyFunc(func(ctx context.Context, to, subject, body string) error {
slog.InfoContext(ctx, "notification",
"to", to,
"subject", subject,
)
return nil
})
Реальный пример: адаптация нескольких сервисов
package notification
import "context"
// --- Email service adapter ---
type SMTPClient struct {
host string
port int
}
func (c *SMTPClient) SendEmail(from, to, subject, html string) error {
// SMTP logic...
return nil
}
type emailAdapter struct {
client *SMTPClient
from string
}
func NewEmailNotifier(client *SMTPClient, from string) Notifier {
return &emailAdapter{client: client, from: from}
}
func (a *emailAdapter) Send(_ context.Context, to, subject, body string) error {
return a.client.SendEmail(a.from, to, subject, body)
}
// --- Telegram bot adapter ---
type TelegramBot struct {
token string
}
func (b *TelegramBot) SendMessage(chatID int64, text string, parseMode string) error {
// Telegram API call...
return nil
}
type telegramAdapter struct {
bot *TelegramBot
chatID int64
}
func NewTelegramNotifier(bot *TelegramBot, chatID int64) Notifier {
return &telegramAdapter{bot: bot, chatID: chatID}
}
func (a *telegramAdapter) Send(_ context.Context, _, subject, body string) error {
text := "*" + subject + "*\n" + body
return a.bot.SendMessage(a.chatID, text, "Markdown")
}
// --- Multi-notifier uses all adapters uniformly ---
type multiNotifier struct {
notifiers []Notifier
}
// NewMultiNotifier sends to all notification channels.
func NewMultiNotifier(notifiers ...Notifier) Notifier {
return &multiNotifier{notifiers: notifiers}
}
func (m *multiNotifier) Send(ctx context.Context, to, subject, body string) error {
for _, n := range m.notifiers {
if err := n.Send(ctx, to, subject, body); err != nil {
return err
}
}
return nil
}
Использование:
func main() {
notifier := notification.NewMultiNotifier(
notification.NewEmailNotifier(smtpClient, "[email protected]"),
notification.NewSlackNotifier(slackAPI, "#alerts"),
notification.NewTelegramNotifier(tgBot, chatID),
)
// One interface for all notification channels
notifier.Send(ctx, "[email protected]", "Alert", "Server is down!")
}
Примеры из стандартной библиотеки
// http.HandlerFunc -- function adapter
http.HandlerFunc(myFunc)
// sort.Interface -- adapts sort algorithm to custom types
sort.Sort(sort.Reverse(mySlice))
// io.ReadCloser from io.Reader + io.Closer
io.NopCloser(reader) // adapts Reader to ReadCloser
Когда использовать
Используйте, когда:
- Интеграция со сторонними библиотеками
- Нужно привести разные API к единому интерфейсу
- Функция должна удовлетворять интерфейсу (функциональный адаптер)
- Миграция с одной библиотеки на другую
Не используйте, когда:
- Можно изменить исходный интерфейс
- Адаптация слишком сложная (возможно, нужен другой паттерн)
- Один-к-одному маппинг без трансформации (просто используйте интерфейс)
Сравнение с похожими паттернами
| Паттерн | Отличие |
|---|---|
| Decorator | Добавляет поведение, сохраняя интерфейс; Adapter меняет интерфейс |
| Facade | Упрощает сложный API; Adapter приводит один интерфейс к другому |
| Proxy | Тот же интерфейс + контроль доступа; Adapter меняет интерфейс |