전략 strategy 패턴 - 자바스크립트 예제

SW설계
읽는데 5분 소요
처음 쓰여진 날: 2025-09-01
마지막 수정일: 2025-09-01

요약

전략 strategy 패턴을 타입스크립트 코드와 함께 알아봅니다.

전략 패턴 요약

패턴 종류핵심 키워드
전략 (Strategy)알고리즘을 캡슐화 , 동적으로 교체
전략 패턴 감자
전략 패턴 감자

전략 (Strategy) 패턴

전략 패턴은 알고리즘군(a family of algorithms)을 정의하고, 각 알고리즘을 캡슐화하여, 필요에 따라 서로 교체해서 사용할 수 있게 만드는 디자인 패턴입니다. 즉, '무엇을' 할 것인지는 Context(문맥) 객체에 그대로 두고, '어떻게' 할 것인지를 다양한 Strategy(전략) 객체에 위임하는 방식입니다.

현실 세계의 예로 '경로 찾기' 를 들 수 있습니다. 서울에서 부산까지 가는 방법에는 KTX, 버스, 비행기 등 여러 가지가 있습니다. 어떤 교통수단을 선택하든 '서울에서 부산으로 이동한다'는 목표(Context)는 동일하지만, 이동 방식(Strategy)은 달라집니다. 전략 패턴은 이처럼 다양한 '방법(알고리즘)'들을 부품처럼 쉽게 갈아 끼울 수 있도록 해줍니다.

기본 구조

  • Context: 전략을 사용하는 주체입니다. 내부에 Strategy 인터페이스에 대한 참조를 가지고 있으며, 클라이언트의 요청을 받으면 자신이 가진 전략 객체에게 작업을 위임합니다.
  • Strategy: 모든 구체적인 전략 클래스들이 구현해야 하는 공통 인터페이스입니다. 보통 알고리즘을 실행하는 단일 메서드(예: execute())를 정의합니다.
  • ConcreteStrategy: Strategy 인터페이스를 구현하여 실제 알고리즘을 정의하는 클래스입니다. (예: BusStrategy, TrainStrategy)

예시: 결제 시스템 구현하기

온라인 쇼핑몰에서 다양한 결제 방법을 지원하는 시스템을 만든다고 가정해봅시다. 사용자는 신용카드, 카카오페이, 네이버페이 등 여러 방법으로 결제할 수 있어야 합니다.

먼저, 모든 결제 전략이 따라야 할 PaymentStrategy 인터페이스를 정의합니다.

javascript
// Strategy: 공통 결제 인터페이스
class PaymentStrategy {
  pay(amount) {
    throw new Error("pay() 메서드는 반드시 구현되어야 합니다.");
  }
}

이제 구체적인 결제 방법들을 ConcreteStrategy로 구현합니다.

javascript
// ConcreteStrategy A: 신용카드 결제
class CreditCardStrategy extends PaymentStrategy {
  constructor(name, cardNumber) {
    super();
    this.name = name;
    this.cardNumber = cardNumber;
  }

  pay(amount) {
    console.log(
      `${amount}원을 ${this.name}님의 신용카드(${this.cardNumber})로 결제합니다.`
    );
    // 실제 카드사 연동 로직...
  }
}

// ConcreteStrategy B: 카카오페이 결제
class KakaoPayStrategy extends PaymentStrategy {
  constructor(email) {
    super();
    this.email = email;
  }

  pay(amount) {
    console.log(`${amount}원을 ${this.email} 카카오페이 계정으로 결제합니다.`);
    // 실제 카카오페이 API 연동 로직...
  }
}

다음으로, 결제 프로세스를 담당할 ShoppingCart(장바구니) 클래스를 Context로 만듭니다.

javascript
// Context: 결제 전략을 사용하는 장바구니
class ShoppingCart {
  constructor(amount) {
    this.amount = amount;
    this.paymentStrategy = null; // 처음에는 전략이 설정되지 않음
  }

  // 클라이언트가 동적으로 전략을 변경할 수 있도록 setter를 제공
  setPaymentStrategy(strategy) {
    this.paymentStrategy = strategy;
  }

  // 결제 실행
  checkout() {
    if (!this.paymentStrategy) {
      console.log("결제 방법이 선택되지 않았습니다.");
      return;
    }
    this.paymentStrategy.pay(this.amount);
  }
}

// 클라이언트 코드
const cart = new ShoppingCart(10000); // 10,000원짜리 상품

// 신용카드로 결제하기
const creditCard = new CreditCardStrategy("김재현", "1234-5678-9012-3456");
cart.setPaymentStrategy(creditCard);
cart.checkout();
// 출력: 10000원을 김재현님의 신용카드(1234-5678-9012-3456)로 결제합니다.

console.log("
--- 결제 방법을 변경합니다 ---");

// 카카오페이로 결제하기
const kakaoPay = new KakaoPayStrategy("test@kakao.com");
cart.setPaymentStrategy(kakaoPay);
cart.checkout();
// 출력: 10000원을 test@kakao.com 카카오페이 계정으로 결제합니다.

ShoppingCart 클래스는 어떤 결제 방법이 선택되었는지 구체적으로 알 필요가 없습니다. 그저 paymentStrategy에 할당된 객체의 pay() 메서드를 호출할 뿐입니다. 만약 '네이버페이'라는 새로운 결제 수단이 추가되어도, NaverPayStrategy 클래스를 새로 만들기만 하면 ShoppingCart 코드는 전혀 수정할 필요가 없습니다.

이처럼 전략 패턴은 **'개방-폐쇄 원칙(OCP)'**을 잘 따르는 설계입니다. 새로운 기능(전략) 추가에는 열려 있고, 기존 코드의 수정에는 닫혀 있습니다.

전략 패턴 중요 키워드 🔑

  • 알고리즘을 캡슐화하고 동적으로 교체할 수 있습니다.
  • '어떻게' 하는가(How)를 '무엇을' 하는가(What)로부터 분리합니다.
  • 위임(Delegation) 을 통해 문제를 해결합니다. (Context가 Strategy에게 위임)
  • if-elseswitch 문으로 가득 찬 코드를 리팩토링하는 좋은 방법입니다.
  • 개방-폐쇄 원칙(OCP)을 만족시킵니다.

정처기 실기 대비 문제

문제
알고리즘 군을 정의하고(추상 클래스) 같은 알고리즘을 각각 하나의 클래스로 캡슐화한 다음, 필요할 때 서로 교환해서 사용할 수 있게 하는 패턴을 보기에서 고르시오
보기
답변
정답정답 보기