C++ State 패턴

객체의 내부 상태에 따라 행동이 달라지는 상태 머신 패턴입니다. 미디어 플레이어의 재생/정지/일시정지 상태 전환을 예시로 구현합니다.

Gist
#include <iostream>
#include <memory>
#include <string>
#include <stdexcept>

// ── 전방 선언 ─────────────────────────────────────────────────────────────────
class MediaPlayer;

// ── 상태 인터페이스 ───────────────────────────────────────────────────────────
class PlayerState {
public:
    virtual ~PlayerState() = default;

    virtual void play(MediaPlayer& player)  = 0;
    virtual void pause(MediaPlayer& player) = 0;
    virtual void stop(MediaPlayer& player)  = 0;
    virtual void next(MediaPlayer& player)  = 0;

    virtual std::string name() const = 0;
};

// ── 컨텍스트: 미디어 플레이어 ────────────────────────────────────────────────
class MediaPlayer {
public:
    explicit MediaPlayer(std::string trackName)
        : trackName_(std::move(trackName)), position_(0) {}

    // 상태 전환 — 상태 객체가 직접 호출
    void setState(std::shared_ptr<PlayerState> newState) {
        std::cout << "  [전환] " << (state_ ? state_->name() : "없음")
                  << " → " << newState->name() << '\n';
        state_ = std::move(newState);
    }

    // 사용자 액션 — 현재 상태에 위임
    void play()  { state_->play(*this);  }
    void pause() { state_->pause(*this); }
    void stop()  { state_->stop(*this);  }
    void next()  { state_->next(*this);  }

    // 트랙 정보 접근자
    const std::string& trackName() const { return trackName_; }
    void setTrack(std::string name) { trackName_ = std::move(name); position_ = 0; }

    int  position() const        { return position_; }
    void advancePosition(int ms) { position_ += ms; }
    void resetPosition()         { position_ = 0; }

    void printStatus() const {
        std::cout << "  [상태] " << (state_ ? state_->name() : "없음")
                  << " | 트랙: " << trackName_
                  << " | 위치: " << position_ << "ms\n";
    }

private:
    std::shared_ptr<PlayerState> state_;
    std::string trackName_;
    int position_;
};

// ── 구체 상태: 정지 ───────────────────────────────────────────────────────────
class StoppedState final : public PlayerState {
public:
    static std::shared_ptr<StoppedState> instance() {
        static auto inst = std::make_shared<StoppedState>();
        return inst;
    }

    void play(MediaPlayer& player) override;   // 전방 선언 — 아래 정의

    void pause(MediaPlayer& player) override {
        std::cout << "  [무시] 정지 상태에서 일시정지 불가\n";
    }

    void stop(MediaPlayer& player) override {
        std::cout << "  [무시] 이미 정지 상태입니다.\n";
    }

    void next(MediaPlayer& player) override;

    std::string name() const override { return "정지"; }
};

// ── 구체 상태: 재생 ───────────────────────────────────────────────────────────
class PlayingState final : public PlayerState {
public:
    static std::shared_ptr<PlayingState> instance() {
        static auto inst = std::make_shared<PlayingState>();
        return inst;
    }

    void play(MediaPlayer& player) override {
        std::cout << "  [무시] 이미 재생 중입니다.\n";
    }

    void pause(MediaPlayer& player) override;  // 아래 정의

    void stop(MediaPlayer& player) override;

    void next(MediaPlayer& player) override;

    std::string name() const override { return "재생"; }
};

// ── 구체 상태: 일시정지 ───────────────────────────────────────────────────────
class PausedState final : public PlayerState {
public:
    static std::shared_ptr<PausedState> instance() {
        static auto inst = std::make_shared<PausedState>();
        return inst;
    }

    void play(MediaPlayer& player) override;

    void pause(MediaPlayer& player) override {
        std::cout << "  [무시] 이미 일시정지 상태입니다.\n";
    }

    void stop(MediaPlayer& player) override;

    void next(MediaPlayer& player) override;

    std::string name() const override { return "일시정지"; }
};

// ── 상태 전환 로직 구현 (모든 클래스 선언 후) ────────────────────────────────

void StoppedState::play(MediaPlayer& p) {
    p.resetPosition();
    p.setState(PlayingState::instance());
    std::cout << "  ▶ '" << p.trackName() << "' 재생 시작\n";
}
void StoppedState::next(MediaPlayer& p) {
    p.setTrack("다음 트랙");
    std::cout << "  ⏭ 다음 트랙으로 전환: '" << p.trackName() << "'\n";
}

void PlayingState::pause(MediaPlayer& p) {
    p.advancePosition(30'000); // 30초 진행 가정
    p.setState(PausedState::instance());
    std::cout << "  ⏸ 일시정지 (위치: " << p.position() << "ms)\n";
}
void PlayingState::stop(MediaPlayer& p) {
    p.resetPosition();
    p.setState(StoppedState::instance());
    std::cout << "  ⏹ 재생 정지\n";
}
void PlayingState::next(MediaPlayer& p) {
    p.setTrack("다음 트랙");
    std::cout << "  ⏭ 재생 중 다음 트랙: '" << p.trackName() << "'\n";
}

void PausedState::play(MediaPlayer& p) {
    p.setState(PlayingState::instance());
    std::cout << "  ▶ '" << p.trackName() << "' 이어서 재생 (위치: " << p.position() << "ms)\n";
}
void PausedState::stop(MediaPlayer& p) {
    p.resetPosition();
    p.setState(StoppedState::instance());
    std::cout << "  ⏹ 일시정지에서 정지\n";
}
void PausedState::next(MediaPlayer& p) {
    p.setTrack("다음 트랙");
    p.setState(StoppedState::instance());
    std::cout << "  ⏭ 다음 트랙으로 전환 후 정지: '" << p.trackName() << "'\n";
}

// ── 사용 예시 ─────────────────────────────────────────────────────────────────
int main() {
    MediaPlayer player("Bohemian Rhapsody");
    player.setState(StoppedState::instance());
    player.printStatus();

    std::cout << "\n--- 재생 시작 ---\n";
    player.play();
    player.printStatus();

    std::cout << "\n--- 일시정지 ---\n";
    player.pause();
    player.printStatus();

    std::cout << "\n--- 이어서 재생 ---\n";
    player.play();
    player.printStatus();

    std::cout << "\n--- 재생 중 다음 트랙 ---\n";
    player.next();
    player.printStatus();

    std::cout << "\n--- 정지 ---\n";
    player.stop();
    player.printStatus();

    std::cout << "\n--- 정지 상태에서 일시정지 시도 (무시) ---\n";
    player.pause();
}