전체 목록
설계Medium#119

디자인 패턴 중 Strategy, Factory, Singleton 패턴을 설명해주세요.

#설계#디자인패턴#Strategy#Factory#Singleton

답변 포인트

알고리즘 교체, 객체 생성 분리, 단일 인스턴스 보장을 기준으로 생각해보세요.

정답 및 해설

빠른 요약

Strategy 패턴은 알고리즘이나 정책을 객체로 분리해 런타임에 교체할 수 있게 합니다. Factory 패턴은 객체 생성 로직을 별도 함수나 클래스로 분리해 클라이언트가 구체 클래스 생성 방식을 몰라도 되게 합니다.

디자인 패턴은 반복해서 등장하는 설계 문제에 대한 검증된 해결 방식입니다. Strategy, Factory, Singleton은 객체지향 설계에서 자주 사용되는 대표적인 패턴입니다.

Strategy Pattern

Strategy 패턴은 알고리즘을 객체로 분리해 런타임에 교체할 수 있게 하는 패턴입니다.

TypeScript
interface DiscountStrategy {
  calculate(price: number): number;
}

class RateDiscount implements DiscountStrategy {
  constructor(private rate: number) {}

  calculate(price: number) {
    return price * (1 - this.rate);
  }
}

class FixedDiscount implements DiscountStrategy {
  constructor(private amount: number) {}

  calculate(price: number) {
    return Math.max(0, price - this.amount);
  }
}

class Order {
  constructor(private discount: DiscountStrategy) {}

  getTotal(price: number) {
    return this.discount.calculate(price);
  }
}

할인 정책이 늘어나도 Order는 변경하지 않고 새 전략 클래스를 추가할 수 있습니다.

적합한 경우:

  • 조건문으로 알고리즘을 많이 분기할 때
  • 정책을 런타임에 바꾸고 싶을 때
  • 테스트하기 쉬운 독립 정책 객체가 필요할 때

Factory Pattern

Factory 패턴은 객체 생성 로직을 별도 함수나 클래스로 분리합니다.

TypeScript
type PaymentType = "card" | "bank";

interface PaymentClient {
  pay(amount: number): Promise<void>;
}

function createPaymentClient(type: PaymentType): PaymentClient {
  switch (type) {
    case "card":
      return new CardPaymentClient();
    case "bank":
      return new BankTransferClient();
  }
}

클라이언트 코드는 구체 클래스 생성 방식을 몰라도 됩니다.

적합한 경우:

  • 생성 과정이 복잡할 때
  • 입력값에 따라 다른 구현체를 생성할 때
  • 구체 클래스 의존을 줄이고 싶을 때

Singleton Pattern

Singleton 패턴은 애플리케이션에서 인스턴스가 하나만 존재하도록 보장합니다.

TypeScript
class Config {
  private static instance: Config;

  private constructor() {}

  static getInstance() {
    if (!Config.instance) {
      Config.instance = new Config();
    }

    return Config.instance;
  }
}

적합한 경우:

  • 설정 객체
  • 로거
  • 커넥션 풀처럼 공유 자원이 필요한 객체

Singleton 주의사항

Singleton은 전역 상태처럼 사용되기 쉬워 테스트를 어렵게 만들 수 있습니다. 숨은 의존성이 생기고, 실행 순서에 따라 상태가 오염될 수 있습니다.

현대 프레임워크에서는 직접 Singleton을 구현하기보다 DI 컨테이너가 객체 생명주기를 관리하게 하는 경우가 많습니다.

정리

패턴목적주의점
Strategy알고리즘/정책 교체작은 정책까지 과도하게 분리하지 않기
Factory객체 생성 책임 분리단순 생성까지 불필요하게 감싸지 않기
Singleton단일 인스턴스 보장전역 상태와 테스트 오염 주의

세 패턴의 공통점과 차이

디자인 패턴은 반복되는 설계 문제에 대한 이름 붙은 해결책입니다. Strategy, Factory, Singleton은 객체 생성과 행위 교체를 다루는 대표적인 패턴입니다.

Strategy

알고리즘을 객체/함수로 분리해 런타임에 교체합니다.

TypeScript
type DiscountStrategy = (price: number) => number;

const vipDiscount: DiscountStrategy = (price) => price * 0.8;
const normalDiscount: DiscountStrategy = (price) => price;

function checkout(price: number, discount: DiscountStrategy) {
  return discount(price);
}

Factory

생성 로직을 한곳에 모아 클라이언트가 구체 클래스를 몰라도 되게 합니다.

TypeScript
function createLogger(env: 'dev' | 'prod') {
  return env === 'prod' ? new CloudLogger() : new ConsoleLogger();
}

Singleton

애플리케이션에서 하나만 존재해야 하는 객체를 공유합니다. 설정, connection pool 등에 쓰이지만 전역 상태가 되어 테스트를 어렵게 만들 수 있습니다.

실무 조언

패턴 이름보다 의존성 방향이 중요합니다. Strategy/Factory는 결합도를 낮추는 데 도움이 되지만, Singleton은 남용하면 숨은 의존성과 상태 공유 문제를 만들 수 있습니다.

관련 질문

같은 카테고리/태그 기준