상태 패턴
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 104개 중 100번째.
상태 패턴을 사용하면 객체의 내부 상태가 변경될 때 객체의 동작을 변경할 수 있으므로, 객체의 class가 변경된 것처럼 보이게 할 수 있습니다. 서로 다른 상태를 처리하기 위해 복잡한 조건문을 사용하는 대신, 각 상태를 별도의 class로 캡슐화합니다.
이 패턴은 현재 상태에 대한 참조를 유지하는 Context, 상태별 동작을 정의하는 상태 interface, 그리고 각 상태에 대한 동작을 구현하는 Concrete 상태로 구성됩니다:
#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() action을 서로 다르게 처리하며 다음 상태로 전환하는 책임을 집니다. Document는 전환 로직을 알 필요가 없습니다. 단지 current 상태에 delegates합니다. 이를 통해 대규모 switch 문이 사라지고 새로운 상태를 간단하게 추가할 수 있습니다.
객체의 동작이 해당 객체의 상태에 크게 의존하고 객체의 상태에 따라 전환되는 조건문이 많을 때 State를 사용하세요.
챌린지
쉬움State 패턴을 사용하여 Traffic Light Controller를 만들어 봅시다. 교통 신호가 서로 다른 상태(Red, Yellow, Green)를 순환하고, 각 상태가 신호가 change될 때 발생하는 일과 표시되는 메시지를 결정하는 시스템을 만들게 됩니다. 이는 State 패턴의 전형적인 예입니다. 교통 신호의 동작은 전적으로 현재 상태에 따라 달라집니다.
코드를 세 개의 파일로 구성합니다.
TrafficLightState.h: 상태 interface와 모든 Concrete 상태 class를 define합니다.Abstract
TrafficLightStateclass를 다음과 같이 만듭니다.- 다음 상태로 전환하는 change를 처리하는 순수 virtual method
change(TrafficLight& light) - 현재 색상을 문자열로 반환하는 순수 virtual method
getColor() - 운전자가 해야 할 일을 반환하는 순수 virtual method
getAction()(예: "Stop", "Caution", "Go") - virtual destructor
세 가지 Concrete 상태를 Implement합니다.
RedState: 색상은"Red", action은"Stop"이며 Green으로 change합니다.YellowState: 색상은"Yellow", action은"Caution"이며 Red로 change합니다.GreenState: 색상은"Green", action은"Go"이며 Yellow로 change합니다.
상태가
TrafficLight를 참조하므로TrafficLight에 대한 forward declaration이 필요합니다.- 다음 상태로 전환하는 change를 처리하는 순수 virtual method
TrafficLight.h: current 상태를 유지하는 context class를 만듭니다.TrafficLightclass는std::unique_ptr<TrafficLightState>를 보유하고, default로 Red 상태에서 시작해야 합니다. 다음을 Implement합니다.setState(std::unique_ptr<TrafficLightState> newState): current 상태를 변경합니다.change(): current 상태의 change method에 delegates합니다.display(): 신호의 상태를[Color]: [Action]형식으로 출력합니다.
main.cpp: 상태를 순환하는 교통 신호를 보여 줍니다.입력 하나를 읽습니다. 수행할 상태 변경 횟수(integer)입니다.
TrafficLight를 만들고 initial 상태를 표시합니다. 그런 다음 지정된 횟수만큼 change를 수행하고, 각 change 후에 상태를 표시합니다.
예를 들어 입력이 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 class는 어떤 일이 발생할지 결정하기 위해 conditional logic을 사용할 필요가 없습니다. 단순히 현재 속한 상태에 delegates합니다.
직접 해보기
#include <iostream>
#include "TrafficLight.h"
using namespace std;
int main() {
// 상태 변경 횟수를 읽습니다
int numChanges;
cin >> numChanges;
// TODO: TrafficLight 객체를 생성하세요
// TODO: 초기 상태를 표시하세요
// TODO: 지정된 횟수만큼 변경을 수행하세요
// 각 변경 후에 현재 상태를 표시하세요
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
직접 연습해 보세요: 온라인 C++ 컴파일러