Adapter Pattern을 실제 예제로 설명

발행: (2025년 12월 7일 오후 09:12 GMT+9)
6 min read
원문: Dev.to

Source: Dev.to

소개

당신은 서드파티 결제 게이트웨이를 애플리케이션에 통합하고 있습니다. 모든 것이 순조롭게 보이다가, 그들의 SDK가 코드베이스가 기대하는 인터페이스와 완전히 다른 형태라는 것을 깨닫게 됩니다. 익숙한 상황인가요? 바로 **Adapter Pattern(어댑터 패턴)**이 빛을 발하는 순간입니다.

어댑터 패턴은 Gang of Four에서 제시한 가장 실용적인 디자인 패턴 중 하나입니다. 호환되지 않는 두 인터페이스 사이에 다리 역할을 하여, 원래는 함께 사용할 수 없던 클래스들이 함께 동작하도록 합니다. 해외 여행 시 전원 어댑터를 사용하는 것과 비슷합니다—기기는 동일하게 동작하지만 어댑터가 호환되지 않는 콘센트를 맞춰 줍니다.

이 글에서는 실제 프로덕션 코드에서 마주칠 수 있는 .NET 예시—결제 게이트웨이, 로깅 시스템, 레거시 코드 통합—를 통해 어댑터 패턴을 살펴보겠습니다.

어댑터 패턴이란?

어댑터 패턴은 클래스의 인터페이스를 클라이언트가 기대하는 다른 인터페이스로 변환합니다. 호환되지 않는 인터페이스 때문에 함께 사용할 수 없던 클래스들을 함께 동작하게 해줍니다.

핵심 구성 요소

  • Target Interface – 클라이언트 코드가 기대하는 인터페이스.
  • Adaptee – 호환되지 않는 인터페이스를 가진 기존 클래스.
  • Adapter – Target과 Adaptee 사이의 간극을 메우는 클래스.
  • Client – Target 인터페이스를 사용하는 코드.

실제 예시 1: 결제 게이트웨이 통합

Target Interface (애플리케이션이 기대하는 인터페이스)

// Your application's payment interface
public interface IPaymentProcessor
{
    Task ProcessPaymentAsync(
        decimal amount,
        string currency,
        string cardToken);
    Task RefundAsync(string transactionId, decimal amount);
}

public record PaymentResult(
    bool Success,
    string TransactionId,
    string? ErrorMessage);

public record RefundResult(
    bool Success,
    string RefundId,
    string? ErrorMessage);

Adaptee (Stripe SDK – 간소화 버전)

// Stripe's SDK has its own conventions
public class StripeClient
{
    public async Task CreateChargeAsync(StripeChargeRequest request)
    {
        // Simulate API call
        await Task.Delay(100);
        return new StripeCharge
        {
            Id = $"ch_{Guid.NewGuid():N}",
            Status = "succeeded",
            Amount = request.AmountInCents
        };
    }

    public async Task CreateRefundAsync(string chargeId, long amountInCents)
    {
        await Task.Delay(100);
        return new StripeRefund
        {
            Id = $"re_{Guid.NewGuid():N}",
            Status = "succeeded"
        };
    }
}

// Stripe uses cents, not decimal amounts!
public class StripeChargeRequest
{
    public long AmountInCents { get; set; }
    public string Currency { get; set; } = "usd";
    public string Source { get; set; } = string.Empty; // Card token
}

public class StripeCharge
{
    public string Id { get; set; } = string.Empty;
    public string Status { get; set; } = string.Empty;
    public long Amount { get; set; }
}

public class StripeRefund
{
    public string Id { get; set; } = string.Empty;
    public string Status { get; set; } = string.Empty;
}

Adapter (간극을 메우는 클래스)

public class StripePaymentAdapter : IPaymentProcessor
{
    private readonly StripeClient _stripeClient;
    private readonly ILogger _logger;

    public StripePaymentAdapter(
        StripeClient stripeClient,
        ILogger logger)
    {
        _stripeClient = stripeClient;
        _logger = logger;
    }

    public async Task ProcessPaymentAsync(
        decimal amount,
        string currency,
        string cardToken)
    {
        try
        {
            var request = new StripeChargeRequest
            {
                AmountInCents = (long)(amount * 100),
                Currency = currency.ToLowerInvariant(),
                Source = cardToken
            };

            var charge = await _stripeClient.CreateChargeAsync(request);

            return new PaymentResult(
                Success: charge.Status == "succeeded",
                TransactionId: charge.Id,
                ErrorMessage: null);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Stripe payment failed");
            return new PaymentResult(
                Success: false,
                TransactionId: string.Empty,
                ErrorMessage: ex.Message);
        }
    }

    public async Task RefundAsync(string transactionId, decimal amount)
    {
        try
        {
            var amountInCents = (long)(amount * 100);
            var refund = await _stripeClient.CreateRefundAsync(transactionId, amountInCents);

            return new RefundResult(
                Success: refund.Status == "succeeded",
                RefundId: refund.Id,
                ErrorMessage: null);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Stripe refund failed");
            return new RefundResult(false, string.Empty, ex.Message);
        }
    }
}

실제 예시 2: 로깅 시스템 어댑터

Target Interface

public interface IAppLogger
{
    void LogInfo(string message, params object[] args);
    void LogWarning(string message, params object[] args);
    void LogError(Exception ex, string message, params object[] args);
    void LogDebug(string message, params object[] args);
}

Adaptees (다양한 로깅 라이브러리)

// Serilog‑style logger
public class SerilogLogger
{
    public void Write(LogLevel level, string template, params object[] values) { }
    public void Write(LogLevel level, Exception ex, string template, params object[] values) { }
}

// Legacy logging library
public class LegacyLogger
{
    public void WriteToLog(string category, string message, int severity) { }
    public void WriteException(string category, Exception ex) { }
}

Adapters

public class SerilogAdapter : IAppLogger
{
    private readonly SerilogLogger _logger;

    public SerilogAdapter(SerilogLogger logger) => _logger = logger;

    public void LogInfo(string message, params object[] args)
        => _logger.Write(LogLevel.Information, message, args);

    public void LogWarning(string message, params object[] args)
        => _logger.Write(LogLevel.Warning, message, args);

    public void LogError(Exception ex, string message, params object[] args)
        => _logger.Write(LogLevel.Error, ex, message, args);

    public void LogDebug(string message, params object[] args)
        => _logger.Write(LogLevel.Debug, message, args);
}

public class LegacyLoggerAdapter : IAppLogger
{
    private readonly LegacyLogger _logger;
    private readonly string _category;

    public LegacyLoggerAdapter(LegacyLogger logger, string category = "Application")
    {
        _logger = logger;
        _category = category;
    }

    public void LogInfo(string message, params object[] args)
        => _logger.WriteToLog(_category, string.Format(message, args), severity: 1);

    public void LogWarning(string message, params object[] args)
        => _logger.WriteToLog(_category, string.Format(message, args), severity: 2);

    public void LogError(Exception ex, string message, params object[] args)
    {
        _logger.WriteToLog(_category, string.Format(message, args), severity: 3);
        _logger.WriteException(_category, ex);
    }

    public void LogDebug(string message, params object[] args)
        => _logger.WriteToLog(_category, string.Format(message, args), severity: 0);
}
Back to Blog

관련 글

더 보기 »