Adapter Pattern을 실제 예제로 설명
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);
}