사앙태 패터언 (상태패턴)

Hunter Joe·2026년 3월 19일

상태 패턴

객체의 내부 상태가 변경될 때 해당 객체가 ctx의 행동을 변경할 수 있도록 하는 패턴
객체가 행동을 변경할 때 객체가 클래스를 변경한 것처럼 보일 수 있다.

  1. 행동을 변경??
  2. 클래스를 변경한 것처럼?? -> 실제로는 변경이 일어나지 않았다는건가?

    궁금증은 아래서

상태 패턴은 객체의 내부상태 변화에 따라 객체의 동작을 변경할 수 있는 패턴
이 패턴은 유한 상태 기계의 개념과 유사
상태 패턴은 패턴 인터페이스에 정의된 메서드 호출을 통해 전략을 전환할 수 있는 전략 패턴으로 해석 가능

FSM 유한 상태 기계?

정해진 상태들 사이를 규칙에 따라 이동하는 기계(하드웨어)
신호등(초록, 노랑, 빨강), 자판기(돈 -> 음료선택(유한)), 개찰구(open-close) 등 ->

상태 패턴은 FSM이라는 이론적 개념을 소프트웨어에서 OOP로 구현한 패턴

주요 개념

  1. 모든 주어진 순간에 프로그램이 속해 있을 수 있는 상태들의 수는 유한하다는 것.
  2. 현재 상태에 따라 프로그램은 특정 상태로 전환 or 전환되지 않을 수 있음
    -> 이러한 전환 규칙들을 천이(transition)이라고 하며 이 규칙들 또한 유한하며 결정적

문제점 feat.절차지향

class Document is
    field state: string
    // …
    method publish() is
        switch (state)
            "draft":
                state = "moderation"
                break
            "moderation":
                if (currentUser.role == "admin")
                    state = "published"
                break
            "published":
                // Do nothing.
                break
    // …

아아.. 이런 코드는 OOP에서 OCP원칙을 위반한다고~ 쓰지말라고!
아래서 한번 절차지향적/객체지향적 장단점에 대해 얘기해봄

조건문들에 기반한 상태 머신의 가장 큰 약점은 상태들과 상태에 의존하는 행동들을 추가할수록 분명해지겠죠?

  • 코드베이스 이빠이 커짐..(설계 단계에서 모든 상태 + 천이를 예측하기란 어려워)
  • 유지 보수 빡셈

해결(HOW)

  1. 상태 패턴은 모든 가능한 상태들에 대해 새로운 클래스를 만든다.
  2. 모든 상태별 행동들을 클래스들로 추출한다.

조금 더 구체적으로는?
3. context(ctx)라는 원래 객체는 모든 행동을 구현하는 대신 "현재 상태"를 나타내는 상태 객체 중 하나에 대한 참조를 저장 + 모든 상태와 관련된 작업을 해당 클래스에 위임

ㅇㅎ 상태를 만들겠군 State합성 합성 영어로는 ? 콤포지숀(Composition)~~~
그리고 State는 합성이니깐 고수준 모듈(interface)이겠고 이거슬 구현한 ConcreteState가 있겠군!

구체적인 해결 With 코드

SUDO

Context = 리모컨 
State<<interface>> = 현재 TV 상태 인터페이스
ConcreteState = 실제 상태들 

TS

일단은 간단하게 켜기/끄기만 구현

interface TVState {
 pressButton(tv: TV): void;
}

// TV off 상태 
class TVOffState implements TVState {
	pressButton(tv: TV): void {
		console.log('티비가 켜짐!');
      	tv.setState(new TVOnState());
	}
}

class TVOnState implements TVState {
	pressButton(tv: TV): void {
		console.log('티비가 꺼짐!');
      	tv.setState(new TVOffState());
	}
}

class TV {
	private state: TVState = new TVOffState();
  
  	setState(state: TVState) {
    	this.state = state; 
    }
  	
  	public pressPower() {
		this.state.pressButton(this);
      	// this를 호출한 객체.
    }
}

// -------CLIENT-------
const tv = new TV();

tv.pressPower(); // off -> on 
tv.pressPower(); // on -> off

여기서 음량 조절 채널 버튼을 추가하면 어떻게 될까?
상태패턴은 유한한 상태지만 많은 상태가 있어야 유의미한거잖아요!

interface TVState {
 pressButton(tv: TV): void;
 // 음량, 채널 
 pressChannel(direction: 'up'| 'down'): void
 pressVolume(direction: 'up'|'down'): void
 // 그외 음소거 버튼 등등등 .. 
}

전략패턴 vs 상태패턴

전략 패턴과 비슷해 보이지만 한 가지 중요한 차이점이 있습니다. 상태 패턴에서의 특정 상태들은 서로를 인식하고, 한 상태에서 다른 상태로 천이(on -> off)를 시작할 수 있지만 전략패턴들은 대부분 서로에 대해 알지 못한다는 것입니다.

전략 패턴은 사용자가 필요에따라 런타임에 바꿔!! 바꿔!! 바꿔!! 가능 (사용자가 갑)
상태 패턴은 사용자가 X "응~ 니가 누르는거에 따라서 알아서 바뀔거야~" (상황이 갑)

솔직히 잘 모르겠음..

궁금증 해결하기

1. 행동을 변경??

const tv = new TV();  // TV 객체는 하나

tv.pressPower();  // "티비가 켜짐!" → ON 상태의 행동
tv.pressPower();  // "티비가 꺼짐!" → OFF 상태의 행동

2. 클래스를 변경한 것처럼?? -> 실제로는 변경이 일어나지 않았다는건가?

상태 패턴을 사용하면 다음과 같은상태를 가진 클래스로 바꾼것처럼 행동한다는 뜻
실제로는 변경없음, 상태만 변경됨

tv.state = new TVOnState()   
tv.state = new TVOffState()  

절차지향 vs 객체지향

근데 꼭 절차지향이 나쁜건 아니거덩요.
언어라는 도구를 잘 활용하면 좋거덩요
꼭 oop에 갇혀 있을 필욘 없거덩요
상황에 맞춰서 쓰면 되거덩요
그러하거덩요

절차지향 C

#include <stdio.h>

// 상태 ENUM
typedef enum {
    TV_OFF,
    TV_ON
} TVState;

// 데이터 구조체 (행동 없음, 데이터만)
typedef struct {
    TVState state;
} TV;

// 함수가 구조체 밖에 존재
void press_power(TV* tv) {
    switch (tv->state) {
        case TV_OFF:
            printf("티비가 켜짐!\n");
            tv->state = TV_ON;
            break;
        case TV_ON:
            printf("티비가 꺼짐!\n");
            tv->state = TV_OFF;
            break;
    }
}

int main() {
    TV tv = { TV_OFF };  // 초기화
    press_power(&tv);    // 포인터로 넘김
    press_power(&tv);
    return 0;
}

또한 상태가 많지 않다면 가독성 + 유지보수성은 절차지향이 편리함 (등가교환)

"뭐가 더 자주 바뀌냐" 에 따라 선택이 달라짐

기능(함수)이 자주 추가 -> 절차지향
타입(클래스)이 자주 추가 -> OOP

절차지향은 자료구조는 바꾸기 어렵지만 함수 추가는 EZPZ
OOP는 자료구조는 바꾸기 쉽지만 함수 추가는 HARD

profile
Improvise, Adapt, Overcome

0개의 댓글