PostCover

디자인 패턴을 쉽게 풀어보자 - 상태 패턴편

GoF의 23가지 디자인 패턴 중 하나인 '상태 패턴(State Pattern)'을 쉽게 풀어보기

상태(State) 패턴

**상태 패턴(State Pattern)**은 객체의 내부 상태에 따라 행동이 바뀌도록 설계하는 행동 디자인 패턴입니다. 이 패턴에서는 상태 객체를 별도로 정의하고, 객체가 상태를 변경할 때 해당 상태에 맞는 행동을 위임합니다. 조건문을 통한 상태 변경 코드의 복잡성을 줄이고, 유연한 상태 전환을 구현할 수 있습니다.

문제점: 상태에 따라 다른 동작을 수행해야 할 때의 코드 복잡성

어떤 객체가 여러 상태를 가지며, 각 상태에 따라 다른 행동을 수행해야 할 때, 상태를 if-elseswitch로 처리하면 코드가 복잡해집니다. 또한 새로운 상태를 추가하거나 수정할 때 코드 변경이 필요합니다.

class TrafficLight {

    private String state = "RED";  // 현재 상태

    public void change() {
        if (state.equals("RED")) {
            System.out.println("Stop!");
            state = "GREEN";
        } else if (state.equals("GREEN")) {
            System.out.println("Go!");
            state = "YELLOW";
        } else if (state.equals("YELLOW")) {
            System.out.println("Caution!");
            state = "RED";
        }
    }
}

public class Main {

    public static void main(String[] args) {
        TrafficLight trafficLight = new TrafficLight();
        trafficLight.change();  // Stop!
        trafficLight.change();  // Go!
        trafficLight.change();  // Caution!
    }
}

문제점

  • 조건문이 많아질수록 코드 복잡도 증가: 새로운 상태를 추가하려면 여러 조건문을 수정해야 합니다.
  • 확장성 문제: 상태별 동작이 명확하게 분리되지 않아 유지보수가 어렵습니다.
  • 객체의 책임 증가: 하나의 객체가 모든 상태와 동작을 관리합니다.

해결 방법: 상태 패턴 적용

상태 패턴은 각 상태를 별도의 클래스로 구현하고, 객체가 상태를 변경할 때 해당 상태 객체에 행동을 위임합니다.

이로써 상태별 로직이 분리되고, 상태 추가와 확장이 쉬워집니다.

상태 인터페이스 정의

interface State {
    void handle(TrafficLight trafficLight);
}

구체적인 상태 클래스 구현

class RedState implements State {

    @Override
    public void handle(TrafficLight trafficLight) {
        System.out.println("Stop!");
        trafficLight.setState(new GreenState());
    }
}

class GreenState implements State {

    @Override
    public void handle(TrafficLight trafficLight) {
        System.out.println("Go!");
        trafficLight.setState(new YellowState());
    }
}

class YellowState implements State {

    @Override
    public void handle(TrafficLight trafficLight) {
        System.out.println("Caution!");
        trafficLight.setState(new RedState());
    }
}

Context 클래스 구현

class TrafficLight {

    private State state;

    public TrafficLight() {
        state = new RedState();  // 초기 상태 설정
    }

    public void setState(State state) {
        this.state = state;
    }

    public void change() {
        state.handle(this);
    }
}

클라이언트 코드

public class Main {

    public static void main(String[] args) {
        TrafficLight trafficLight = new TrafficLight();

        trafficLight.change();  // Stop!
        trafficLight.change();  // Go!
        trafficLight.change();  // Caution!
        trafficLight.change();  // Stop!
    }
}

상태 패턴의 동작 원리

  1. Context 객체는 상태를 유지하며, 상태 객체에 행동을 위임합니다.
  2. 각 State 객체는 해당 상태에 맞는 동작을 구현하며, 필요할 때 다음 상태로 전환합니다.
  3. 상태 변경은 객체 내부에서 자동으로 이루어지며, 클라이언트는 상태 객체의 내부 구현을 알 필요가 없습니다.

상태 패턴의 장단점

장점

  • 코드 복잡도 감소: 상태별 행동을 별도의 클래스로 분리하여 조건문을 제거합니다.
  • 확장성 증가: 새로운 상태를 쉽게 추가할 수 있으며, 기존 코드를 수정할 필요가 없습니다.
  • 유지보수 용이: 상태별 로직이 분리되어 코드 가독성이 향상됩니다.
  • 객체의 책임 분리: 상태별로 객체의 행동을 정의하여 단일 책임 원칙(SRP)을 준수합니다.

단점

  • 클래스 수 증가: 각 상태마다 클래스를 만들어야 하므로 클래스 수가 많아질 수 있습니다.
  • 간단한 경우 오히려 복잡도 증가: 상태가 많지 않은 경우에는 불필요하게 복잡해질 수 있습니다.

결론

상태 패턴은 객체의 행동이 상태에 따라 달라지는 상황에서 사용하기 적합한 패턴입니다. 이 패턴을 사용하면 조건문을 제거하고 상태별 로직을 명확하게 분리할 수 있습니다.

다만, 상태가 많지 않은 간단한 경우에는 불필요한 복잡도를 추가할 수 있으므로, 상태 변화가 복잡한 시스템에 적합합니다. GUI 컴포넌트, 게임 개발, 트랜잭션 관리와 같은 영역에서 자주 사용됩니다.