State 패턴: 상태가 늘어나는 흐름 다루기
10강. State 패턴: 상태가 늘어나는 흐름 다루기
1. 이번 강의에서 해결할 문제
상태별 if가 모든 메서드에 흩어지면 새 상태 하나가 여러 switch를 수정하게 합니다.
2. 학습 목표
3. 핵심 개념
State는 현재 상태가 행동과 다음 전이를 결정하게 합니다. Strategy와 구조는 비슷하지만 State는 시간에 따른 전이 책임, Strategy는 교체 가능한 정책이 목적입니다. 상태가 적고 닫혀 있으면 enum switch가 더 단순합니다.
상태 전이는 단순 값 변경이 아니라 “현재 상태에서 이 행동을 허용하는가”라는 규칙입니다. Todo → InProgress → Completed는 허용하지만 Todo → Completed는 거부할 수 있습니다. 이 규칙이 여러 메서드의 switch에 반복될 때 State 객체가 각 상태의 허용 행동과 다음 상태를 맡습니다.
4. 가장 작은 예제로 시작하기
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string_view>
class TaskState {
public:
virtual ~TaskState() = default;
virtual std::unique_ptr<TaskState> start() const = 0;
virtual std::unique_ptr<TaskState> complete() const = 0;
virtual std::string_view name() const noexcept = 0;
};
class InProgressState;
class CompletedState;
class TodoState final : public TaskState {
public:
std::unique_ptr<TaskState> start() const override;
std::unique_ptr<TaskState> complete() const override {
throw std::logic_error("Todo는 바로 완료할 수 없습니다.");
}
std::string_view name() const noexcept override { return "Todo"; }
};
class InProgressState final : public TaskState {
public:
std::unique_ptr<TaskState> start() const override {
throw std::logic_error("이미 진행 중입니다.");
}
std::unique_ptr<TaskState> complete() const override;
std::string_view name() const noexcept override { return "InProgress"; }
};
class CompletedState final : public TaskState {
public:
std::unique_ptr<TaskState> start() const override {
throw std::logic_error("완료 작업은 다시 시작할 수 없습니다.");
}
std::unique_ptr<TaskState> complete() const override {
throw std::logic_error("이미 완료됐습니다.");
}
std::string_view name() const noexcept override { return "Completed"; }
};
std::unique_ptr<TaskState> TodoState::start() const {
return std::make_unique<InProgressState>();
}
std::unique_ptr<TaskState> InProgressState::complete() const {
return std::make_unique<CompletedState>();
}
class Task {
std::unique_ptr<TaskState> state_ = std::make_unique<TodoState>();
public:
void start() { state_ = state_->start(); }
void complete() { state_ = state_->complete(); }
std::string_view status() const noexcept { return state_->name(); }
};
int main() {
Task task;
std::cout << task.status() << '\n';
task.start();
std::cout << task.status() << '\n';
task.complete();
std::cout << task.status() << '\n';
}
예상 출력: Todo, InProgress, Completed가 차례로 한 줄씩 출력됩니다.
5. 코드가 동작하는 이유
Task는 현재 상태를 unique_ptr로 하나만 소유합니다. start()의 오른쪽 전이 함수가 성공해 새 객체를 반환한 뒤에만 state_가 교체됩니다. 예외가 나면 대입이 실행되지 않아 기존 상태가 유지됩니다.
변경 전에는 start, complete, cancel마다 상태 switch가 반복됩니다. 변경 후 Todo의 규칙은 TodoState, 진행 중 규칙은 InProgressState에서 찾습니다. 단, 상태가 두 개이고 전이가 고정된 작은 모델에서는 enum과 한 switch가 더 읽기 쉬울 수 있습니다.
6. 자주 하는 실수와 해결법
7. 직접 실습
8. 이해 점검 질문 3개
9. 핵심 요약
10. 다음 강의 연결
시간에 따라 바뀌는 전이 규칙을 State로 분리했습니다. 다음 강의에서는 상태 흐름과 무관하게 교체되는 계산 정책을 Strategy로 설계합니다.
State 패턴: 상태가 늘어나는 흐름 다루기 미니 퀴즈
선택 즉시 정답과 해설을 확인할 수 있습니다. 결과는 이 브라우저에만 저장됩니다.
학습을 마쳤나요?
직접 실습과 점검 질문까지 확인한 뒤 완료로 표시하세요.