IT 지식 목록
IT 지식

대규모 데이터 처리의 효율을 극대화하는 전략: 전략 패턴과 어댑터 패턴의 실무적 설계 비교

전략 패턴과 어댑터 패턴을 비교 분석하여 유연하고 확장 가능한 소프트웨어 설계를 구현하는 방법을 설명합니다.

00
대규모 데이터 처리의 효율을 극대화하는 전략: 전략 패턴과 어댑터 패턴의 실무적 설계 비교

소프트웨어 아키텍처를 설계하다 보면, 동일한 비즈니스 로직을 서로 다른 환경이나 데이터 소스에 적용해야 하는 상황을 자주 마주하게 됩니다. 이때 흔히 발생하는 실수는 if-else나 switch 문을 남용하여 코드의 응집도를 낮추는 것입니다. 2026년 현재, 유지보수성과 확장성이 곧 기술 부채를 결정짓는 핵심 지표가 된 시점에서, 전략 패턴(Strategy Pattern)과 어댑터 패턴(Adapter Pattern)의 설계 철학을 이해하는 것은 엔지니어의 필수 소양입니다.

개념 소개: 왜 이 두 패턴인가?

전략 패턴은 '어떻게(How)'를 바꾸는 것에 집중합니다. 알고리즘군을 정의하고 각각을 캡슐화하여 교체 가능하게 만듭니다. 반면, 어댑터 패턴은 '무엇(What)'을 맞추는 것에 집중합니다. 호환되지 않는 인터페이스를 가진 객체들이 함께 동작할 수 있도록 변환하는 역할을 합니다. 전략 패턴은 알고리즘의 유연성을 제공하고, 어댑터 패턴은 시스템 간의 연결성을 제공합니다.

핵심 원리 설명: 비유와 다이어그램

전략 패턴을 '운송 수단'에 비유해 봅시다. 물류 시스템에서 화물을 목적지까지 옮길 때, 상황에 따라 트럭, 선박, 항공기라는 알고리즘을 바꿔 낄 수 있습니다. 클라이언트는 '운송'이라는 메서드만 호출할 뿐, 내부적으로 어떤 수단을 쓰는지 알 필요가 없습니다.

어댑터 패턴은 '해외 여행 시의 멀티 어댑터'입니다. 한국의 220V 콘센트 기기를 110V 전압을 사용하는 일본에서 쓰려면 변환기가 필요하듯, 라이브러리나 외부 API의 규격이 우리 코드의 인터페이스와 맞지 않을 때 중간에서 다리 역할을 수행합니다.

구조 비교:

  • 전략 패턴: Context -> StrategyInterface <- ConcreteStrategyA/B
  • 어댑터 패턴: Client -> TargetInterface -> Adapter -> Adaptee

코드 예제: 파이썬을 활용한 설계 구현

먼저, 전략 패턴을 사용하여 할인 정책을 유연하게 교체하는 예제입니다.

# 전략 패턴: 할인 정책 관리
class DiscountStrategy:
    def calculate(self, price): pass

class VIPDiscount(DiscountStrategy):
    def calculate(self, price): return price * 0.8  # 20% 할인

class RegularDiscount(DiscountStrategy):
    def calculate(self, price): return price * 0.9  # 10% 할인

class Order:
    def __init__(self, price, strategy: DiscountStrategy):
        self.price = price
        self.strategy = strategy
    
    def get_final_price(self):
        return self.strategy.calculate(self.price)

# 사용 예시
order = Order(10000, VIPDiscount())
print(f"최종 가격: {order.get_final_price()}")

다음은 레거시 외부 시스템의 API 규격을 새로운 서비스 인터페이스에 맞추는 어댑터 패턴 예제입니다.

# 어댑터 패턴: 외부 라이브러리 호환
class NewSystemInterface:
    def get_data(self): pass

# 기존 레거시 시스템
class LegacyService:
    def fetch_xml_data(self):
        return "<data>123</data>"

# 어댑터: Legacy를 NewSystem 규격으로 변환
class LegacyAdapter(NewSystemInterface):
    def __init__(self, legacy_service):
        self.legacy_service = legacy_service
        
    def get_data(self):
        xml = self.legacy_service.fetch_xml_data()
        # XML을 파싱하여 JSON 형태로 변환하는 로직 (가상)
        return int(xml.split('>')[1].split('<')[0])

adapter = LegacyAdapter(LegacyService())
print(f"데이터 값: {adapter.get_data()}")

실무 적용 사례

전략 패턴은 결제 게이트웨이(PG) 연동 시 자주 사용됩니다. 카카오페이, 토스페이, 신용카드 결제 모듈은 서로 다른 구현을 가지지만, '결제 실행'이라는 동일한 전략 인터페이스를 구현함으로써 시스템이 특정 PG사에 종속되지 않게 합니다.

어댑터 패턴은 레거시 시스템을 점진적으로 마이크로서비스 아키텍처(MSA)로 전환할 때 필수적입니다. 외부 데이터베이스의 결과값을 우리 도메인 모델로 매핑하거나, 버전이 다른 외부 API 응답을 우리 서비스의 표준 규격으로 변환할 때 사용합니다.

자주 하는 실수와 해결법

가장 흔한 실수는 '모든 것에 패턴을 적용하려는 시도'입니다. 전략 패턴을 적용할 필요가 없는 단순한 로직에 복잡한 클래스 구조를 도입하면 가독성만 떨어집니다.

  • 실수 1: 클래스 폭발. 전략 패턴을 사용하다 보면 너무 많은 작은 클래스가 생성됩니다.
    • 해결: 파이썬의 경우 함수를 일급 객체로 취급하므로, 복잡한 클래스 대신 함수를 인자로 넘기는 방식으로 전략을 간단히 구현할 수 있습니다.
  • 실수 2: 어댑터가 비즈니스 로직을 포함함. 어댑터는 순수하게 '변환'만 담당해야 합니다.
    • 해결: 어댑터 내부에서 데이터 가공이나 비즈니스 연산을 하지 마세요. 가공은 서비스 계층에서 처리하도록 분리해야 합니다.

더 공부할 리소스 추천

  1. Refactoring.Guru: 디자인 패턴의 시각적 설명과 코드가 가장 잘 정리된 곳입니다.
  2. Clean Code (Robert C. Martin): 전략 패턴을 통해 'Open-Closed Principle'을 어떻게 실현하는지 깊이 있게 다룹니다.
  3. Enterprise Integration Patterns: 복잡한 시스템 간 통신에서의 패턴을 공부하고 싶다면 필독서입니다.