프로그래밍/알고리즘&자료구조&패턴

디자인패턴 - 상태 패턴

lazy_web_devloper 2025. 8. 18. 10:56
728x90
반응형

JAVA로 구현하는 알고리즘과 디자인 패턴 공부: 상태 패턴 (State)

1. 상태 패턴이란? 🚦

개념: 객체의 내부 상태가 변경될 때마다 그 행동을 바꿀 수 있게 하는 패턴입니다. 상태별로 다른 행동들을 각각의 상태 클래스로 캡슐화하고, 주체 객체(Context)는 자신의 현재 상태를 나타내는 상태 객체에게 행동을 위임합니다.

자판기를 생각하면 이해하기 쉽습니다. 자판기는 자신의 상태에 따라 완전히 다르게 행동합니다.

  • '동전 없음' 상태: 돈을 넣으라는 메시지만 보여줍니다.
  • '동전 있음' 상태: 음료 버튼을 누를 수 있습니다.
  • '음료수 품절' 상태: 돈을 넣어도 음료 버튼이 작동하지 않습니다.

이처럼, 자판기(Context)는 하나지만, 그 내부 상태(NoCoinState, HasCoinState, SoldOutState)가 무엇이냐에 따라 버튼을 눌렀을 때의 결과가 완전히 달라집니다.

2. 왜 사용할까요?

  • 상태에 따른 조건문 제거: 상태에 따라 행동을 분기하는 거대한 if-else나 switch 문을 깔끔하게 제거할 수 있습니다. 각 상태에 관련된 로직은 해당 상태 클래스 안에 캡슐화됩니다.
  • 개방-폐쇄 원칙 (OCP) 충족: 새로운 상태가 추가되더라도, 기존의 상태 클래스나 컨텍스트 클래스를 수정할 필요 없이 새로운 상태 클래스만 추가하면 됩니다.
  • 상태 전환 로직의 명확화: 각 상태 클래스가 다음 상태로 전환되는 책임을 직접 가질 수 있어, 상태 변경 로직을 한 곳에서 명확하게 관리할 수 있습니다.

3. Java로 구현하는 방법

시나리오: 온라인 문서의 게시 워크플로우를 만들어 보겠습니다. 문서는 '초고(Draft)', '검토 중(Moderation)', '게시됨(Published)' 상태를 가지며, 각 상태에서 '게시(publish)' 버튼을 눌렀을 때의 행동이 다릅니다.

1단계: 상태(State) 인터페이스 정의

모든 상태 클래스가 구현해야 할 공통 규약입니다. 각 상태에서 가능한 행동들을 메서드로 정의합니다.

Java
 
// 모든 상태가 구현할 공통 인터페이스
public interface State {
    void publish(Document document);
}

2단계: 구체적인 상태(Concrete State) 클래스 구현

각각의 상태에 해당하는 행동과 상태 전환 로직을 구현합니다.

Java
 
// 상태 1: 초고 상태
public class DraftState implements State {
    @Override
    public void publish(Document document) {
        System.out.println("'초고' 상태의 문서를 '검토 중' 상태로 변경합니다.");
        // 상태를 '검토 중'으로 전환
        document.changeState(new ModerationState());
    }
}

// 상태 2: 검토 중 상태
public class ModerationState implements State {
    @Override
    public void publish(Document document) {
        System.out.println("'검토 중' 상태의 문서를 '게시됨' 상태로 변경합니다.");
        // 상태를 '게시됨'으로 전환
        document.changeState(new PublishedState());
    }
}

// 상태 3: 게시됨 상태
public class PublishedState implements State {
    @Override
    public void publish(Document document) {
        System.out.println("이미 '게시됨' 상태이므로 아무 작업도 하지 않습니다.");
        // 상태 변경 없음
    }
}

3단계: 컨텍스트(Context) 클래스 구현

상태를 가지는 주체 객체입니다. 현재 상태를 나타내는 멤버 변수를 가지며, 행동 요청이 오면 현재 상태 객체에 그 행동을 위임합니다.

Java
 
public class Document {
    // 현재 상태를 저장하는 변수
    private State state;

    public Document() {
        // 문서는 초기에 '초고' 상태로 시작
        this.state = new DraftState();
    }

    // 상태를 변경하는 메서드
    public void changeState(State state) {
        this.state = state;
    }

    // 행동 요청이 오면 현재 상태 객체에 위임한다.
    public void publish() {
        state.publish(this);
    }
}

4단계: 클라이언트 코드에서 사용하기

클라이언트는 컨텍스트 객체를 생성하고, 행동을 요청하며 상태가 변하는 것을 확인합니다.

Java
 
public class Main {
    public static void main(String[] args) {
        // 1. 새로운 문서 생성 (초기 상태: Draft)
        Document document = new Document();
        
        // 2. 첫 번째 게시 요청
        // 현재 상태가 DraftState이므로, ModerationState로 상태가 전환된다.
        document.publish(); // '초고' -> '검토 중'

        // 3. 두 번째 게시 요청
        // 현재 상태가 ModerationState이므로, PublishedState로 상태가 전환된다.
        document.publish(); // '검토 중' -> '게시됨'

        // 4. 세 번째 게시 요청
        // 현재 상태가 PublishedState이므로, 아무 일도 일어나지 않는다.
        document.publish(); // 변화 없음
    }
}

4. 상태 패턴과 실제 사례

  • TCP/IP 프로토콜: TCP 연결은 LISTEN, SYN-SENT, ESTABLISHED, FIN-WAIT, CLOSED 등 다양한 상태를 가집니다. 각 상태에서 데이터를 받거나 특정 신호를 받았을 때의 행동과 다음 상태가 명확하게 정의되어 있어, 상태 패턴으로 모델링하기에 완벽합니다.
  • UI 컴포넌트: 마우스 커서가 올라갔을 때(Hover), 클릭했을 때(Pressed), 비활성화되었을 때(Disabled) 등 UI 컴포넌트의 상태에 따라 모양이나 행동이 바뀌는 것을 상태 패턴으로 구현할 수 있습니다.
  • 게임 캐릭터: 캐릭터가 '정상', '독', '마비', '기절' 등 다양한 상태 이상에 걸렸을 때, 각 상태에 따라 행동이 제약되거나 추가적인 데미지를 입는 로직을 상태 패턴으로 깔끔하게 관리할 수 있습니다.

5. 문제 제시 및 답변 💡

문제: 신호등 시스템을 만드려고 합니다. 신호등은 '빨간불', '노란불', '초록불'의 3가지 상태를 가집니다. 시간이 지나면 change() 메서드가 호출되고, 신호등은 다음 상태로 바뀌어야 합니다 ('초록불' → '노란불' → '빨간불' → '초록불' ...).

상태 패턴을 사용하여 이 신호등 시스템을 어떻게 설계할 수 있을까요?

답변:

문서 게시 예제와 거의 동일한 구조로 설계할 수 있습니다.

  1. TrafficLightState 인터페이스: 모든 신호등 상태가 구현할 공통 인터페이스를 만듭니다. change(TrafficLight light) 메서드를 가집니다.
  2. 구체적인 상태 클래스:
    • GreenLightState: change() 메서드가 호출되면, 신호등의 상태를 YellowLightState로 변경합니다.
    • YellowLightState: change() 메서드가 호출되면, 신호등의 상태를 RedLightState로 변경합니다.
    • RedLightState: change() 메서드가 호출되면, 신호등의 상태를 GreenLightState로 변경합니다.
  3. TrafficLight 클래스 (Context): 현재 상태를 저장하는 TrafficLightState 타입의 멤버 변수를 가집니다. change() 메서드가 호출되면, 현재 상태 객체의 change() 메서드에 자기 자신을 넘겨주어 상태 전환을 위임합니다.

이렇게 설계하면, TrafficLight 클래스는 복잡한 if (currentState == RED) { ... } else if (currentState == GREEN) { ... } 같은 조건문 없이, 오직 현재 상태 객체에게 모든 행동과 상태 전환의 책임을 맡기므로 매우 깔끔한 코드를 유지할 수 있습니다.

728x90
반응형