상태 패턴과 전략 패턴, 어떻게 구분하고 선택해야 할까?

서론: 비슷해 보이지만 본질이 다른 두 디자인 패턴

소프트웨어 설계에서 상태 패턴(State Pattern)과 전략 패턴(Strategy Pattern)은 종종 혼동된다. 두 패턴 모두 인터페이스를 기반으로 다형성을 활용하며, UML 클래스 다이어그램 상으로는 거의 동일한 구조를 가진다. 그러나 이 둘은 추구하는 목적과 적용되는 시나리오가 근본적으로 다르다. 어떤 패턴을 선택하느냐는 단순히 코딩 스타일의 차이가 아니라, 도메인을 바라보는 관점의 차이를 반영한다.

전략 패턴: 알고리즘의 교체

전략 패턴은 다양한 알고리즘을 캡슐화하고, 필요에 따라 서로 교체할 수 있도록 하는 패턴이다. 주로 조건문(if-else 또는 switch)으로 인해 복잡해지는 로직을 정리할 때 사용된다.

핵심 개념

  • 목적: 유사한 기능을 수행하지만 방식이 다른 알고리즘들을 하나의 인터페이스로 통합하고, 실행 시점에 선택 가능하게 함.
  • 장점: 새로운 전략 추가 시 기존 코드 수정 없이 확장 가능 (OCP 원칙 준수).
  • 사용 시기: 동일한 문제를 해결하기 위한 여러 접근 방법이 존재하고, 클라이언트가 직접 전략을 선택하거나 주입해야 할 때.

코드 예제

public interface PaymentMethod {
    void pay(double amount);
}

public class CreditCardPayment implements PaymentMethod {
    public void pay(double amount) {
        System.out.println("신용카드로 " + amount + "원 결제");
    }
}

public class KakaoPayPayment implements PaymentMethod {
    public void pay(double amount) {
        System.out.println("카카오페이로 " + amount + "원 결제");
    }
}

public class PaymentProcessor {
    private PaymentMethod method;

    public void setPaymentMethod(PaymentMethod method) {
        this.method = method;
    }

    public void execute(double amount) {
        if (method == null) throw new IllegalStateException("결제 수단 미지정");
        method.pay(amount);
    }
}

클라이언트는 필요에 따라 CreditCardPaymentKakaoPayPayment를 선택하여 PaymentProcessor에 주입할 수 있다. 이 선택은 외부 결정이며, 전략 간에는 자연스러운 전환 흐름이 없다.

상태 패턴: 상태 기반 행동 변화

상태 패턴은 객체의 내부 상태가 변경됨에 따라 그 행동이 달라져야 할 때 사용된다. 객체가 특정 상태에 있을 때는 특정한 방식으로 동작해야 하며, 상태 전이는 객체 스스로 또는 외부 트리거에 의해 발생한다.

핵심 개념

  • 목적: 상태 변화에 따른 행동 변화를 명시적으로 모델링하고, 조건문을 제거함.
  • 특징: 상태 간 전이(transition)가 핵심이며, 전이는 일반적으로 객체 내부 로직이나 메서드 호출 결과로 발생한다.
  • 사용 시기: 객체의 행동이 현재 상태에 강하게 의존하며, 상태 전이 규칙이 복잡할 때.

개선된 코드 예제

public interface DeviceState {
    void powerPress(DeviceContext context);
    void volumeUp(DeviceContext context);
}

public class OnState implements DeviceState {
    public void powerPress(DeviceContext context) {
        System.out.println("전원 끔");
        context.setState(new OffState());
    }

    public void volumeUp(DeviceContext context) {
        System.out.println("볼륨 증가");
    }
}

public class OffState implements DeviceState {
    public void powerPress(DeviceContext context) {
        System.out.println("전원 켬");
        context.setState(new OnState());
    }

    public void volumeUp(DeviceContext context) {
        System.out.println("전원 꺼짐 - 무시");
    }
}

public class DeviceContext {
    private DeviceState state = new OffState();

    private void setState(DeviceState state) {
        this.state = state;
    }

    public void pressPower() {
        state.powerPress(this);
    }

    public void increaseVolume() {
        state.volumeUp(this);
    }
}

여기서 중요한 점은 상태 전이가 DeviceState 구현체 내부에서 context.setState(...)를 통해 제어된다는 것이다. 클라이언트는 단순히 pressPower()를 호출할 뿐이며, 어떤 상태로 전이될지는 상태 객체 자체가 결정한다.

핵심 차이점 요약

항목 전략 패턴 상태 패턴
의도 알고리즘 교체 상태 기반 행동 변화
전이 제어 클라이언트가 전략 선택 내부 상태 또는 컨텍스트가 제어
의존성 전략은 컨텍스트를 참조하지 않음 상태는 컨텍스트를 참조 가능 (전이 위해)
확장성 새 전략 추가 쉬움 새 상태 및 전이 규칙 추가 필요
예시 정렬 알고리즘, 결제 수단 자동차 변속기, 문서 편집기 상태

어떻게 선택해야 할까?

패턴 선택은 다음과 같은 질문을 통해 결정할 수 있다:

  • 행동의 변화가 사용자의 선택인지, 아니면 시스템의 자연스러운 흐름인가?
    → 사용자가 선택하면 전략, 시스템 상태 변화면 상태 패턴.
  • 객체가 스스로 상태를 전이시키는가?
    → 그렇다면 상태 패턴이 적합하다.
  • 각 구현체 간에 평등한 관계인가, 계층/흐름이 있는가?
    → 평등하면 전략, 순차적 흐름 있으면 상태.

예를 들어, "미국에서는 영어를, 한국에서는 한국어를 사용한다"라는 요구사항을 생각해보자. 만약 사용자가 국가별 언어를 수동으로 선택한다면 LanguageStrategy가 적절하다. 하지만 사용자가 국경을 넘을 때 언어가 자동으로 전환된다면, 위치를 나타내는 LocationState로 모델링하는 것이 더 자연스럽다.

마무리

전략 패턴과 상태 패턴은 구조적으로 유사하지만, 의미 체계와 적용 맥락은 다르다. 전략은 "어떻게 할 것인가"에 대한 선택이고, 상태는 "지금 어떤 상황인가"에 따른 반응이다. 올바른 패턴을 선택하면 코드의 가독성과 유지보수성이 크게 향상되며, 도메인의 본질을 더 잘 드러낼 수 있다.

태그: 디자인패턴 상태패턴 전략패턴 자바 OOP

7월 25일 06:01에 게시됨