전략 패턴이란?

객체의 특정 행동(알고리즘)을 직접 구현하지 않고, 외부에서 ‘전략’을 주입받아 갈아 끼우는 방식

쉽게 말해 “본체는 그대로 두고, 부품만 바꿔서 기능을 바꾸는 것"이라 보면 됨

전략 패턴이 적용되지 않은 코드

전략 패턴을 쓰지 않으면 보통 이렇게 구현하게 됨

class Vehicle {
  constructor(private type: "road" | "rail") {}

  move() {
    if (this.type === "road") {
      console.log("부릉부릉 도로로 감 🚌");
    } else if (this.type === "rail") {
      console.log("칙칙폭폭 선로로 감 🚂");
    }
  }
}

이 방식이 가지는 문제는 다음과 같음

  • 조건문이 계속 늘어남 새로운 이동 방식이 생길 때마다 move()를 수정해야 함 → OCP(Open-Closed Principle) 위반

  • Vehicle 클래스가 알고리즘에 강하게 결합됨 → 이동 로직이 Vehicle 내부에 박혀 있음

  • 유닛 테스트 어려움 Vehicle을 테스트하려면 항상 type 조건을 따라가야 함

  • 코드 재사용성 낮음 “선로 전략”을 다른 곳에서 재사용할 수도 없고, 로직 분리가 안 됨

그러면 이 문제를 전략 패턴은 어떻게 해결할 수 있을까?

구조 설계

Vehicle(본체)은 MoveStrategy(부품)를 가지고 있고, 필요할 때 이 부품만 바꿈

classDiagram
    class MoveStrategy {
        <<interface>>
        +move()
    }

    class RailStrategy {
        +move()
    }
    class RoadStrategy {
        +move()
    }

    class Vehicle {
        -strategy: MoveStrategy
        +setStrategy()
        +move()
    }

    MoveStrategy <|.. RailStrategy
    MoveStrategy <|.. RoadStrategy
    Vehicle o-- MoveStrategy

TypeScript 구현

1. 전략(Strategy) 정의

“어떻게 움직일지"에 대한 부품들임

// 행동의 규격 (인터페이스)
interface MoveStrategy {
  move(): void;
}

// 부품 1: 도로용
class RoadStrategy implements MoveStrategy {
  move() {
    console.log("부릉부릉 도로로 감 🚌");
  }
}

// 부품 2: 선로용
class RailStrategy implements MoveStrategy {
  move() {
    console.log("칙칙폭폭 선로로 감 🚂");
  }
}

2. 컨텍스트(Context) 정의

전략을 사용하는 본체임. setStrategy로 부품만 갈아 끼우면 됨

class Vehicle {
  // 생성할 때 초기 전략(부품)을 낌
  constructor(private strategy: MoveStrategy) {}

  // 핵심: 실행 중에 부품 교체 가능!
  setStrategy(strategy: MoveStrategy) {
    this.strategy = strategy;
  }

  // 본체는 그냥 부품한테 일을 시킴 (위임)
  move() {
    this.strategy.move();
  }
}

3. 사용 예시

Vehicle 코드를 뜯어고칠 필요 없이, 주입하는 객체만 바꾸면 행동이 변함

// 1. 처음엔 도로 전략으로 생성
const bus = new Vehicle(new RoadStrategy());
bus.move(); // 결과: 부릉부릉 도로로 감 🚌

// 2. 갑자기 선로로 가야 함? 전략만 교체!
bus.setStrategy(new RailStrategy());
bus.move(); // 결과: 칙칙폭폭 선로로 감 🚂

핵심 요약

  • 코드 수정 없음 (OCP): 새로운 이동 방식(예: FlyStrategy)이 생겨도 Vehicle 클래스는 수정할 필요가 없음. 그냥 새 전략 만들어서 끼우면 됨
  • 유연성: 실행 중(Runtime)에 setStrategy로 즉시 행동을 바꿀 수 있음
  • 상속보단 합성: 복잡하게 클래스 상속(extends) 하지 말고, 기능을 부품처럼 인터페이스로 받아서 쓰는 게 훨씬 유연함

참고자료