Menu
Coddy logo textTech

상태 패턴

Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 100번째.

상태 패턴(State pattern)은 객체의 내부 상태가 변경될 때 객체가 동작을 변경할 수 있도록 허용하며, 마치 객체의 클래스가 바뀐 것처럼 보이게 합니다. 여러 상태를 처리하기 위해 복잡한 조건문을 사용하는 대신, 각 상태를 별도의 클래스로 캡슐화합니다.

이 패턴은 현재 상태에 대한 참조를 유지하는 Context, 상태별 동작을 정의하는 State 인터페이스, 그리고 각 상태에 대한 동작을 구현하는 Concrete States로 구성됩니다:

#include <iostream>
#include <memory>

class Document;  // 전방 선언

// 상태 인터페이스
class DocumentState {
public:
    virtual void publish(Document& doc) = 0;
    virtual std::string getName() const = 0;
    virtual ~DocumentState() = default;
};

// 컨텍스트
class Document {
    std::unique_ptr<DocumentState> state;
public:
    Document();
    void setState(std::unique_ptr<DocumentState> newState) {
        state = std::move(newState);
    }
    void publish() { state->publish(*this); }
    std::string getStateName() const { return state->getName(); }
};

// 구체적인 상태들
class Draft : public DocumentState {
public:
    void publish(Document& doc) override;
    std::string getName() const override { return "Draft"; }
};

class Review : public DocumentState {
public:
    void publish(Document& doc) override;
    std::string getName() const override { return "Review"; }
};

class Published : public DocumentState {
public:
    void publish(Document& doc) override {
        std::cout << "Already published\n";
    }
    std::string getName() const override { return "Published"; }
};

void Draft::publish(Document& doc) {
    std::cout << "Moving to review\n";
    doc.setState(std::make_unique<Review>());
}

void Review::publish(Document& doc) {
    std::cout << "Publishing document\n";
    doc.setState(std::make_unique<Published>());
}

Document::Document() : state(std::make_unique<Draft>()) {}

각 상태는 publish() 동작을 다르게 처리하며 다음 상태로의 전환을 책임집니다. Document는 전환 로직을 알 필요가 없습니다 - 단순히 현재 상태에 위임할 뿐입니다. 이것은 거대한 switch 문을 제거하고 새로운 상태를 추가하는 것을 간단하게 만듭니다.

객체의 행동이 상태에 크게 의존하고, 객체의 상태에 따라 전환되는 조건문이 많을 때 State 패턴을 사용하세요.

challenge icon

챌린지

쉬움

상태(State) 패턴을 사용하여 신호등 컨트롤러(Traffic Light Controller)를 만들어 보겠습니다. 신호등이 서로 다른 상태(Red, Yellow, Green)를 순환하며, 각 상태가 신호가 바뀔 때 일어나는 일과 표시되는 메시지를 결정하는 시스템을 만들 것입니다. 이것은 상태 패턴의 전형적인 예시로, 신호등의 동작은 전적으로 현재 상태에 따라 달라집니다.

코드는 다음 세 개의 파일로 구성됩니다:

  • TrafficLightState.h: 상태 인터페이스와 모든 구체적인 상태 클래스들을 정의합니다.

    다음 내용을 포함하는 추상 클래스 TrafficLightState를 생성하세요:

    • 다음 상태로의 전환을 처리하는 순수 가상 메서드 change(TrafficLight& light)
    • 현재 색상을 문자열로 반환하는 순수 가상 메서드 getColor()
    • 운전자가 해야 할 행동(예: "Stop", "Caution", "Go")을 반환하는 순수 가상 메서드 getAction()
    • 가상 소멸자

    세 가지 구체적인 상태를 구현하세요:

    • RedState — 색상은 "Red", 행동은 "Stop"이며, Green으로 변경됩니다.
    • YellowState — 색상은 "Yellow", 행동은 "Caution"이며, Red로 변경됩니다.
    • GreenState — 색상은 "Green", 행동은 "Go"이며, Yellow로 변경됩니다.

    상태 클래스들이 TrafficLight를 참조하므로 TrafficLight에 대한 전방 선언(forward declaration)이 필요합니다.

  • TrafficLight.h: 현재 상태를 유지하는 컨텍스트(context) 클래스를 생성합니다.

    TrafficLight 클래스는 std::unique_ptr<TrafficLightState>를 보유해야 하며 기본적으로 Red 상태에서 시작해야 합니다. 다음을 구현하세요:

    • setState(std::unique_ptr<TrafficLightState> newState) — 현재 상태를 변경합니다.
    • change() — 현재 상태의 change 메서드에 작업을 위임합니다.
    • display() — 신호등의 상태를 [Color]: [Action] 형식으로 출력합니다.
  • main.cpp: 신호등이 상태를 순환하는 것을 보여줍니다.

    하나의 입력을 읽습니다: 수행할 상태 변경 횟수(정수).

    TrafficLight를 생성하고 초기 상태를 표시합니다. 그런 다음 지정된 횟수만큼 변경을 수행하며, 각 변경 후의 상태를 표시합니다.

예를 들어, 입력이 3인 경우:

Red: Stop
Green: Go
Yellow: Caution
Red: Stop

입력이 6인 경우:

Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: Stop

신호등이 Red → Green → Yellow → Red와 같이 예측 가능한 패턴으로 상태를 순환하는 방식에 주목하세요. 각 상태는 다음에 어떤 상태가 올지 알고 있으며 자신의 전환을 직접 처리합니다. TrafficLight 클래스는 어떤 일이 일어날지 결정하기 위한 조건부 로직이 필요하지 않으며, 단순히 현재 상태에 작업을 위임할 뿐입니다.

치트 시트

상태 패턴(State pattern)은 객체의 내부 상태가 변경될 때 객체가 동작을 변경할 수 있도록 합니다. 복잡한 조건문을 사용하는 대신, 각 상태를 별도의 클래스로 캡슐화합니다.

이 패턴은 세 가지 구성 요소로 이루어집니다:

  • 상태 인터페이스(State interface): 상태별 동작을 정의합니다.
  • 구체적인 상태(Concrete States): 각 상태에 대한 동작을 구현합니다.
  • 컨텍스트(Context): 현재 상태에 대한 참조를 유지합니다.

기본 구조:

#include <memory>

class Context;  // Forward declaration

// State interface
class State {
public:
    virtual void handleAction(Context& ctx) = 0;
    virtual std::string getName() const = 0;
    virtual ~State() = default;
};

// Context
class Context {
    std::unique_ptr<State> state;
public:
    void setState(std::unique_ptr<State> newState) {
        state = std::move(newState);
    }
    void performAction() { 
        state->handleAction(*this); 
    }
    std::string getStateName() const { 
        return state->getName(); 
    }
};

// Concrete State
class ConcreteState : public State {
public:
    void handleAction(Context& ctx) override {
        // Handle action and transition to next state
        ctx.setState(std::make_unique<AnotherState>());
    }
    std::string getName() const override { 
        return "ConcreteState"; 
    }
};

각 상태는 동작을 다르게 처리하며 다음 상태로의 전이를 책임집니다. 컨텍스트(Context)는 전이 로직을 알 필요 없이 현재 상태에 동작을 위임합니다.

객체의 동작이 상태에 크게 의존하고 거대한 조건문을 피하고 싶을 때 상태 패턴을 사용하세요.

직접 해보기

#include <iostream>
#include "TrafficLight.h"

using namespace std;

int main() {
    // 상태 변경 횟수를 읽습니다
    int numChanges;
    cin >> numChanges;
    
    // TODO: TrafficLight 객체를 생성합니다
    
    // TODO: 초기 상태를 표시합니다
    
    // TODO: 지정된 횟수만큼 변경을 수행합니다
    // 각 변경 후에 현재 상태를 표시합니다
    
    return 0;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨