Structural Created: 2026-01-27 Updated: 2026-01-27

Adapter/Wrapper

A pattern that allows incompatible interfaces to work together by wrapping one interface to match another.

Contributors

When to Use

  • When integrating third-party libraries with incompatible interfaces
  • When standardizing interfaces across different implementations
  • When you want to swap implementations without changing client code

When NOT to Use

  • When you control both interfaces and can change them directly
  • For simple one-to-one method calls without transformation
  • When the performance overhead of wrapping is unacceptable

The Adapter pattern allows incompatible interfaces to work together by creating a wrapper that translates one interface into another. In Go, this is commonly achieved using interface implementation and composition.

Implementation

package storage

import (
    "context"
    "errors"
)

// Storage is our application's interface
type Storage interface {
    Get(ctx context.Context, key string) ([]byte, error)
    Set(ctx context.Context, key string, value []byte) error
}

// RedisClient represents a third-party Redis library
type RedisClient struct {
    addr string
}

func (r *RedisClient) GET(key string) (string, error) {
    // Redis-specific implementation
    return "", nil
}

func (r *RedisClient) SET(key, value string) error {
    return nil
}

// RedisAdapter adapts RedisClient to our Storage interface
type RedisAdapter struct {
    client *RedisClient
}

func NewRedisAdapter(client *RedisClient) Storage {
    return &RedisAdapter{client: client}
}

func (a *RedisAdapter) Get(ctx context.Context, key string) ([]byte, error) {
    if err := ctx.Err(); err != nil {
        return nil, err
    }
    
    value, err := a.client.GET(key)
    if err != nil {
        return nil, err
    }
    return []byte(value), nil
}

func (a *RedisAdapter) Set(ctx context.Context, key string, value []byte) error {
    if err := ctx.Err(); err != nil {
        return err
    }
    
    return a.client.SET(key, string(value))
}

// MemoryCache is another third-party implementation
type MemoryCache struct {
    data map[string][]byte
}

func (m *MemoryCache) Read(key string) ([]byte, bool) {
    val, ok := m.data[key]
    return val, ok
}

func (m *MemoryCache) Write(key string, value []byte) {
    m.data[key] = value
}

// MemoryAdapter adapts MemoryCache to our Storage interface
type MemoryAdapter struct {
    cache *MemoryCache
}

func NewMemoryAdapter(cache *MemoryCache) Storage {
    return &MemoryAdapter{cache: cache}
}

func (a *MemoryAdapter) Get(ctx context.Context, key string) ([]byte, error) {
    value, ok := a.cache.Read(key)
    if !ok {
        return nil, errors.New("key not found")
    }
    return value, nil
}

func (a *MemoryAdapter) Set(ctx context.Context, key string, value []byte) error {
    a.cache.Write(key, value)
    return nil
}

Usage

package main

import (
    "context"
)

type Application struct {
    storage storage.Storage
}

func NewApplication(storage storage.Storage) *Application {
    return &Application{storage: storage}
}

func (app *Application) SaveUser(ctx context.Context, userID string, data []byte) error {
    return app.storage.Set(ctx, userID, data)
}

func main() {
    ctx := context.Background()
    
    // Use Redis in production
    redisClient := &storage.RedisClient{addr: "localhost:6379"}
    prodApp := NewApplication(storage.NewRedisAdapter(redisClient))
    prodApp.SaveUser(ctx, "user1", []byte("data"))
    
    // Use memory cache in tests
    memCache := &storage.MemoryCache{data: make(map[string][]byte)}
    testApp := NewApplication(storage.NewMemoryAdapter(memCache))
    testApp.SaveUser(ctx, "user1", []byte("data"))
}

Benefits

  • Interface Compatibility: Makes incompatible interfaces work together
  • Flexibility: Easy to swap implementations without changing client code
  • Testability: Simplifies testing by allowing mock implementations