상태 패턴
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 패턴을 사용하세요.
챌린지
쉬움상태(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 클래스는 어떤 일이 일어날지 결정하기 위한 조건부 로직이 필요하지 않으며, 단순히 현재 상태에 모든 것을 위임합니다.
직접 해보기
#include <iostream>
#include "TrafficLight.h"
using namespace std;
int main() {
// 상태 변경 횟수를 읽습니다
int numChanges;
cin >> numChanges;
// TODO: TrafficLight 객체를 생성합니다
// TODO: 초기 상태를 출력합니다
// TODO: 지정된 횟수만큼 변경을 수행합니다
// 각 변경 후 현재 상태를 출력합니다
return 0;
}이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.