GoFデザインパターン23個を完全に理解するブログ #20 State(ステート)

2025/11/15

プログラミング

t f B! P L
eyecatch State(ステート)は、オブジェクトの状態(State)によって、振る舞いを変えるデザインパターンです。 if や switch で状態分岐を書く代わりに、状態ごとに クラスを分けて管理するのが特徴的。 「状態の追加・変更に強い」設計ができるので、自動販売機の状態変化でのサンプルパターンで学習を進めましょう。

よく使われる場面

・テキストエディタの入力モード ・キャラクターのアニメーション状態(Idle / Run / Jump) ・ネットワーク接続状態(Connected / Disconnected)

サンプルコード

次のような自動販売機の状態遷移をイメージしてください。
1. 待機中(Idle) 2. お金投入中(HasMoney) 3. 商品を出す(Dispensing)

Stateインターフェイス

class State { insertCoin() {} pressButton() {} dispense() {} }

各Stateの実装

class IdleState extends State { constructor(vendingMachine) { super(); this.vendingMachine = vendingMachine; } insertCoin() { console.log("コインが投入されました。"); this.vendingMachine.setState(this.vendingMachine.hasMoneyState); } } class HasMoneyState extends State { constructor(vendingMachine) { super(); this.vendingMachine = vendingMachine; } pressButton() { console.log("ボタンが押されました。商品を出します。"); this.vendingMachine.setState(this.vendingMachine.dispensingState); } } class DispensingState extends State { constructor(vendingMachine) { super(); this.vendingMachine = vendingMachine; } dispense() { console.log("商品を出しました。次のお客様を待機します。"); this.vendingMachine.setState(this.vendingMachine.idleState); } }

コンテキスト(本体)

class VendingMachine { constructor() { this.idleState = new IdleState(this); this.hasMoneyState = new HasMoneyState(this); this.dispensingState = new DispensingState(this); this.state = this.idleState; } setState(state) { this.state = state; } insertCoin() { this.state.insertCoin(); } pressButton() { this.state.pressButton(); } dispense() { this.state.dispense(); } }

実行

const machine = new VendingMachine(); machine.insertCoin(); // コインが投入されました。 machine.pressButton(); // ボタンが押されました。商品を出します。 machine.dispense(); // 商品を出しました。次のお客様を待機します。

役割

役割 説明
State(状態) 振る舞いを定義するインターフェイス(抽象クラス)
ConcreteState 状態ごとの具体的な処理(Idle / HasMoney / Dispensing)
Context(文脈) 現在の状態を保持し、状態に応じて処理を委譲するクラス

メリット

・if / switch の分岐が消える(条件分岐地獄を防ぐ)。 ・新しい状態を追加しても他のコードを壊さない。 ・オブジェクトの状態変化が明示的になり、デバッグしやすい。

デメリット

・状態ごとにクラスが増える(クラス数が多くなりがち)。 ・シンプルなロジックではオーバーエンジニアリングに見える。

あとがき

処理をそれぞれclass化することで、段階的に依存関係を減らして、それぞれの拡張などの以後運用を楽にする印象です。 順番をさらにコントロールする、デザインパターンのファサードと組み合わせることで、よりしっかりとした設計が組み立てられそうですね。

おまけコード

canvas描画つきサンプルを作ってみました。

説明

3つの状態(Idle / Walk / Attack)をStateクラスで切り替え。 Canvas上のキャラクターが状態ごとに動きやアニメーションを変える。 ボタンを押すたびにhero.setState()で状態を変更。

sample.html

<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>State Pattern Canvas Demo</title> <style> body { text-align: center; font-family: sans-serif; background: #fafafa; } canvas { border: 1px solid #ccc; margin-top: 10px; background: white; } button { margin: 5px; padding: 8px 16px; font-size: 14px; } </style> </head> <body> <h2>🎮 Stateパターン Canvasデモ</h2> <p>ボタンでキャラクターの状態を切り替え</p> <div> <button id="idleBtn">Idle</button> <button id="walkBtn">Walk</button> <button id="attackBtn">Attack</button> </div> <canvas id="gameCanvas" width="400" height="200"></canvas> <script> // === State基底クラス === class State { enter() {} update(ctx, x, y) {} } // === 各状態のクラス === class IdleState extends State { update(ctx, x, y) { ctx.fillStyle = 'gray'; ctx.beginPath(); ctx.arc(x, y, 20, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = 'black'; ctx.fillText('Idle', x - 15, y + 40); } } class WalkState extends State { constructor() { super(); this.offset = 0; } update(ctx, x, y) { this.offset += 0.1; const dx = Math.sin(this.offset) * 5; ctx.fillStyle = 'blue'; ctx.fillRect(x + dx - 15, y - 15, 30, 30); ctx.fillStyle = 'black'; ctx.fillText('Walk', x - 15, y + 40); } } class AttackState extends State { constructor() { super(); this.frame = 0; } update(ctx, x, y) { this.frame++; const size = 20 + Math.sin(this.frame * 0.3) * 10; ctx.fillStyle = 'red'; ctx.beginPath(); ctx.arc(x, y, size, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = 'black'; ctx.fillText('Attack', x - 20, y + 40); } } // === コンテキスト === class Character { constructor() { this.x = 200; this.y = 100; this.state = new IdleState(); } setState(state) { this.state = state; } update(ctx) { this.state.update(ctx, this.x, this.y); } } // === 実行部 === const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); ctx.font = '14px sans-serif'; const hero = new Character(); document.getElementById('idleBtn').onclick = () => hero.setState(new IdleState()); document.getElementById('walkBtn').onclick = () => hero.setState(new WalkState()); document.getElementById('attackBtn').onclick = () => hero.setState(new AttackState()); function loop() { ctx.clearRect(0, 0, canvas.width, canvas.height); hero.update(ctx); requestAnimationFrame(loop); } loop(); </script> </body> </html>

デモ

ボタンでキャラクターの状態を切り替え

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ