0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

全30回:静的と動的でどう違うのか、JavaとPythonで学ぶデザインパターン - Day 22 Stateパターン:オブジェクトの状態によって振る舞いを変える

Posted at

はじめに

皆さん、こんにちは!「JavaとPythonで比べるデザインパターン」シリーズの第22回目です。
今回は、オブジェクトの内部状態に応じてその振る舞いを変化させるState(ステート)パターンについて解説します。


Stateパターンとは?

Stateパターンは、オブジェクトの内部状態を、その状態を表す専用のオブジェクトとしてカプセル化する振る舞いパターンです。これにより、オブジェクトのメソッド内に大量のif-else文やswitch文を書くことを避け、よりクリーンで拡張しやすいコードを実現します。

例えるなら、ATMの操作画面です。
ATMは「カード挿入待ち」「PIN入力中」「取引選択中」「現金引き出し中」など複数の状態を持ち、各状態で利用可能な操作が異なります。同じ「確認ボタン」を押しても、PIN入力中なら認証処理が実行され、引き出し中なら取引確定処理が実行されます。

このパターンの主な目的は以下の通りです:

  • 複雑性の軽減: 状態遷移のロジックを状態クラスに分散させ、メインクラスのコードをシンプルにする
  • 拡張性の向上: 新しい状態や状態遷移を追加する際に、既存のコードを変更する必要がない
  • 責任の明確化: 各状態クラスが、その状態でのみ実行される特定の振る舞いを担う
  • 状態遷移の管理: 複雑な状態遷移ルールを各状態クラスに委譲できる

パターンの構成要素

Stateパターンでは、以下の3つのコンポーネントが登場します:

  1. コンテキスト(Context): 状態に依存する振る舞いを持つメインクラス。現在の状態オブジェクトへの参照を保持
  2. ステートインターフェース(State): 全ての状態クラスに共通の振る舞いを定義
  3. 具象ステートクラス(ConcreteState): ステートインターフェースを実装し、特定の状態での振る舞いを定義

Javaでの実装:厳格な型システムと安全な状態管理

Javaでは、インターフェースと列挙型を組み合わせることで、型安全な状態管理を実現できます。以下に、ATMシステムを例にした実装を示します。

// JavaでのStateパターンの実装例

import java.util.HashMap;
import java.util.Map;

// 状態の種類を列挙型で定義
enum StateType {
    WAITING_FOR_CARD,
    WAITING_FOR_PIN,
    ACCOUNT_MENU,
    TRANSACTION_IN_PROGRESS,
    DISPENSING_CASH,
    ERROR
}

// ステートインターフェース
interface ATMState {
    void insertCard(ATMContext context, String cardNumber);
    void enterPIN(ATMContext context, String pin);
    void selectTransaction(ATMContext context, String transactionType);
    void confirmTransaction(ATMContext context);
    void cancelTransaction(ATMContext context);
    void ejectCard(ATMContext context);
    
    StateType getStateType();
    String getDisplayMessage();
}

// コンテキストクラス
class ATMContext {
    private ATMState currentState;
    private String cardNumber;
    private String accountBalance = "¥1,000,000"; // 簡略化
    private String selectedTransaction;
    private Map<StateType, ATMState> states;
    
    public ATMContext() {
        initializeStates();
        this.currentState = states.get(StateType.WAITING_FOR_CARD);
    }
    
    private void initializeStates() {
        states = new HashMap<>();
        states.put(StateType.WAITING_FOR_CARD, new WaitingForCardState());
        states.put(StateType.WAITING_FOR_PIN, new WaitingForPINState());
        states.put(StateType.ACCOUNT_MENU, new AccountMenuState());
        states.put(StateType.TRANSACTION_IN_PROGRESS, new TransactionInProgressState());
        states.put(StateType.DISPENSING_CASH, new DispensingCashState());
        states.put(StateType.ERROR, new ErrorState());
    }
    
    public void setState(StateType stateType) {
        ATMState newState = states.get(stateType);
        if (newState != null) {
            System.out.println("State transition: " + currentState.getStateType() + 
                             " -> " + stateType);
            this.currentState = newState;
            displayCurrentState();
        }
    }
    
    public void displayCurrentState() {
        System.out.println(">>> " + currentState.getDisplayMessage());
    }
    
    // 委譲メソッド
    public void insertCard(String cardNumber) { currentState.insertCard(this, cardNumber); }
    public void enterPIN(String pin) { currentState.enterPIN(this, pin); }
    public void selectTransaction(String transactionType) { 
        currentState.selectTransaction(this, transactionType); 
    }
    public void confirmTransaction() { currentState.confirmTransaction(this); }
    public void cancelTransaction() { currentState.cancelTransaction(this); }
    public void ejectCard() { currentState.ejectCard(this); }
    
    // ゲッターとセッター
    public String getCardNumber() { return cardNumber; }
    public void setCardNumber(String cardNumber) { this.cardNumber = cardNumber; }
    public String getAccountBalance() { return accountBalance; }
    public String getSelectedTransaction() { return selectedTransaction; }
    public void setSelectedTransaction(String selectedTransaction) { 
        this.selectedTransaction = selectedTransaction; 
    }
    public StateType getCurrentStateType() { return currentState.getStateType(); }
}

// 具象状態クラス群
class WaitingForCardState implements ATMState {
    @Override
    public void insertCard(ATMContext context, String cardNumber) {
        System.out.println("Card inserted: " + cardNumber);
        context.setCardNumber(cardNumber);
        context.setState(StateType.WAITING_FOR_PIN);
    }
    
    @Override
    public void enterPIN(ATMContext context, String pin) {
        System.out.println("Please insert your card first.");
    }
    
    @Override
    public void selectTransaction(ATMContext context, String transactionType) {
        System.out.println("Please insert your card first.");
    }
    
    @Override
    public void confirmTransaction(ATMContext context) {
        System.out.println("Please insert your card first.");
    }
    
    @Override
    public void cancelTransaction(ATMContext context) {
        System.out.println("No transaction to cancel.");
    }
    
    @Override
    public void ejectCard(ATMContext context) {
        System.out.println("No card to eject.");
    }
    
    @Override
    public StateType getStateType() { return StateType.WAITING_FOR_CARD; }
    
    @Override
    public String getDisplayMessage() { return "Please insert your card"; }
}

class WaitingForPINState implements ATMState {
    @Override
    public void insertCard(ATMContext context, String cardNumber) {
        System.out.println("Card already inserted.");
    }
    
    @Override
    public void enterPIN(ATMContext context, String pin) {
        System.out.println("PIN entered: " + "*".repeat(pin.length()));
        // 簡略化:常に認証成功とする
        if (pin.length() == 4) {
            System.out.println("PIN validated successfully.");
            context.setState(StateType.ACCOUNT_MENU);
        } else {
            System.out.println("Invalid PIN format.");
            context.setState(StateType.ERROR);
        }
    }
    
    @Override
    public void selectTransaction(ATMContext context, String transactionType) {
        System.out.println("Please enter your PIN first.");
    }
    
    @Override
    public void confirmTransaction(ATMContext context) {
        System.out.println("Please enter your PIN first.");
    }
    
    @Override
    public void cancelTransaction(ATMContext context) {
        System.out.println("Cancelling... Ejecting card.");
        context.setState(StateType.WAITING_FOR_CARD);
    }
    
    @Override
    public void ejectCard(ATMContext context) {
        System.out.println("Ejecting card...");
        context.setState(StateType.WAITING_FOR_CARD);
    }
    
    @Override
    public StateType getStateType() { return StateType.WAITING_FOR_PIN; }
    
    @Override
    public String getDisplayMessage() { return "Please enter your PIN"; }
}

class AccountMenuState implements ATMState {
    @Override
    public void insertCard(ATMContext context, String cardNumber) {
        System.out.println("Card already inserted.");
    }
    
    @Override
    public void enterPIN(ATMContext context, String pin) {
        System.out.println("Already authenticated.");
    }
    
    @Override
    public void selectTransaction(ATMContext context, String transactionType) {
        System.out.println("Transaction selected: " + transactionType);
        context.setSelectedTransaction(transactionType);
        context.setState(StateType.TRANSACTION_IN_PROGRESS);
    }
    
    @Override
    public void confirmTransaction(ATMContext context) {
        System.out.println("Please select a transaction first.");
    }
    
    @Override
    public void cancelTransaction(ATMContext context) {
        System.out.println("Returning to card insertion...");
        context.setState(StateType.WAITING_FOR_CARD);
    }
    
    @Override
    public void ejectCard(ATMContext context) {
        System.out.println("Thank you for using our ATM.");
        context.setState(StateType.WAITING_FOR_CARD);
    }
    
    @Override
    public StateType getStateType() { return StateType.ACCOUNT_MENU; }
    
    @Override
    public String getDisplayMessage() { 
        return "Account Menu - Balance: " + "¥1,000,000"; 
    }
}

class TransactionInProgressState implements ATMState {
    @Override
    public void insertCard(ATMContext context, String cardNumber) {
        System.out.println("Transaction in progress. Please wait.");
    }
    
    @Override
    public void enterPIN(ATMContext context, String pin) {
        System.out.println("Transaction in progress. Please wait.");
    }
    
    @Override
    public void selectTransaction(ATMContext context, String transactionType) {
        System.out.println("Transaction already selected: " + context.getSelectedTransaction());
    }
    
    @Override
    public void confirmTransaction(ATMContext context) {
        System.out.println("Processing " + context.getSelectedTransaction() + "...");
        if ("Cash Withdrawal".equals(context.getSelectedTransaction())) {
            context.setState(StateType.DISPENSING_CASH);
        } else {
            System.out.println("Transaction completed successfully.");
            context.setState(StateType.ACCOUNT_MENU);
        }
    }
    
    @Override
    public void cancelTransaction(ATMContext context) {
        System.out.println("Transaction cancelled. Returning to menu.");
        context.setState(StateType.ACCOUNT_MENU);
    }
    
    @Override
    public void ejectCard(ATMContext context) {
        System.out.println("Please complete or cancel the current transaction first.");
    }
    
    @Override
    public StateType getStateType() { return StateType.TRANSACTION_IN_PROGRESS; }
    
    @Override
    public String getDisplayMessage() { 
        return "Confirm transaction: Cash Withdrawal ¥10,000"; 
    }
}

class DispensingCashState implements ATMState {
    @Override
    public void insertCard(ATMContext context, String cardNumber) {
        System.out.println("Please wait, dispensing cash...");
    }
    
    @Override
    public void enterPIN(ATMContext context, String pin) {
        System.out.println("Please wait, dispensing cash...");
    }
    
    @Override
    public void selectTransaction(ATMContext context, String transactionType) {
        System.out.println("Please wait, dispensing cash...");
    }
    
    @Override
    public void confirmTransaction(ATMContext context) {
        System.out.println("Cash dispensed. Please take your money and card.");
        context.setState(StateType.WAITING_FOR_CARD);
    }
    
    @Override
    public void cancelTransaction(ATMContext context) {
        System.out.println("Cannot cancel while dispensing cash.");
    }
    
    @Override
    public void ejectCard(ATMContext context) {
        System.out.println("Please take your cash first.");
    }
    
    @Override
    public StateType getStateType() { return StateType.DISPENSING_CASH; }
    
    @Override
    public String getDisplayMessage() { return "Please take your cash"; }
}

class ErrorState implements ATMState {
    @Override
    public void insertCard(ATMContext context, String cardNumber) {
        System.out.println("System error. Please contact support.");
    }
    
    @Override
    public void enterPIN(ATMContext context, String pin) {
        System.out.println("System error. Resetting...");
        context.setState(StateType.WAITING_FOR_CARD);
    }
    
    @Override
    public void selectTransaction(ATMContext context, String transactionType) {
        System.out.println("System error. Please contact support.");
    }
    
    @Override
    public void confirmTransaction(ATMContext context) {
        System.out.println("System error. Please contact support.");
    }
    
    @Override
    public void cancelTransaction(ATMContext context) {
        System.out.println("Resetting system...");
        context.setState(StateType.WAITING_FOR_CARD);
    }
    
    @Override
    public void ejectCard(ATMContext context) {
        System.out.println("Ejecting card due to error...");
        context.setState(StateType.WAITING_FOR_CARD);
    }
    
    @Override
    public StateType getStateType() { return StateType.ERROR; }
    
    @Override
    public String getDisplayMessage() { return "System Error - Please contact support"; }
}

// 使用例
public class Main {
    public static void main(String[] args) {
        ATMContext atm = new ATMContext();
        
        // 正常なトランザクション フロー
        System.out.println("=== ATM Transaction Demo ===");
        atm.insertCard("1234-5678-9012-3456");
        atm.enterPIN("1234");
        atm.selectTransaction("Cash Withdrawal");
        atm.confirmTransaction();
        atm.confirmTransaction(); // 現金受取確認
        
        System.out.println("\n=== Error Handling Demo ===");
        atm.insertCard("9876-5432-1098-7654");
        atm.enterPIN("12"); // 不正なPIN
        atm.cancelTransaction(); // エラーからの回復
    }
}

Pythonでの実装:柔軟性と動的な状態管理

Pythonでは、言語の動的な特性を活かして、より柔軟なStateパターンの実装が可能です。以下に、同じATMシステムをPythonで実装します。

# PythonでのStateパターン実装例

from abc import ABC, abstractmethod
from enum import Enum, auto
from typing import Dict, Optional
import logging

# ログ設定
logging.basicConfig(level=logging.INFO, format='%(message)s')
logger = logging.getLogger(__name__)

class StateType(Enum):
    WAITING_FOR_CARD = auto()
    WAITING_FOR_PIN = auto()
    ACCOUNT_MENU = auto()
    TRANSACTION_IN_PROGRESS = auto()
    DISPENSING_CASH = auto()
    ERROR = auto()

# ステートインターフェース
class ATMState(ABC):
    @abstractmethod
    def insert_card(self, context: 'ATMContext', card_number: str) -> None: pass
    
    @abstractmethod
    def enter_pin(self, context: 'ATMContext', pin: str) -> None: pass
    
    @abstractmethod
    def select_transaction(self, context: 'ATMContext', transaction_type: str) -> None: pass
    
    @abstractmethod
    def confirm_transaction(self, context: 'ATMContext') -> None: pass
    
    @abstractmethod
    def cancel_transaction(self, context: 'ATMContext') -> None: pass
    
    @abstractmethod
    def eject_card(self, context: 'ATMContext') -> None: pass
    
    @property
    @abstractmethod
    def state_type(self) -> StateType: pass
    
    @property
    @abstractmethod
    def display_message(self) -> str: pass

# コンテキストクラス
class ATMContext:
    def __init__(self):
        self._current_state: ATMState = None
        self._card_number: Optional[str] = None
        self._account_balance = "¥1,000,000"
        self._selected_transaction: Optional[str] = None
        self._states: Dict[StateType, ATMState] = {}
        
        self._initialize_states()
        self.set_state(StateType.WAITING_FOR_CARD)
    
    def _initialize_states(self) -> None:
        """状態オブジェクトを初期化"""
        self._states = {
            StateType.WAITING_FOR_CARD: WaitingForCardState(),
            StateType.WAITING_FOR_PIN: WaitingForPINState(),
            StateType.ACCOUNT_MENU: AccountMenuState(),
            StateType.TRANSACTION_IN_PROGRESS: TransactionInProgressState(),
            StateType.DISPENSING_CASH: DispensingCashState(),
            StateType.ERROR: ErrorState()
        }
    
    def set_state(self, state_type: StateType) -> None:
        """状態を変更"""
        new_state = self._states.get(state_type)
        if new_state:
            if self._current_state:
                logger.info(f"State transition: {self._current_state.state_type.name} -> {state_type.name}")
            self._current_state = new_state
            self.display_current_state()
    
    def display_current_state(self) -> None:
        """現在の状態を表示"""
        logger.info(f">>> {self._current_state.display_message}")
    
    # 委譲メソッド
    def insert_card(self, card_number: str) -> None:
        self._current_state.insert_card(self, card_number)
    
    def enter_pin(self, pin: str) -> None:
        self._current_state.enter_pin(self, pin)
    
    def select_transaction(self, transaction_type: str) -> None:
        self._current_state.select_transaction(self, transaction_type)
    
    def confirm_transaction(self) -> None:
        self._current_state.confirm_transaction(self)
    
    def cancel_transaction(self) -> None:
        self._current_state.cancel_transaction(self)
    
    def eject_card(self) -> None:
        self._current_state.eject_card(self)
    
    # プロパティ
    @property
    def card_number(self) -> Optional[str]:
        return self._card_number
    
    @card_number.setter
    def card_number(self, value: str) -> None:
        self._card_number = value
    
    @property
    def account_balance(self) -> str:
        return self._account_balance
    
    @property
    def selected_transaction(self) -> Optional[str]:
        return self._selected_transaction
    
    @selected_transaction.setter
    def selected_transaction(self, value: str) -> None:
        self._selected_transaction = value
    
    @property
    def current_state_type(self) -> StateType:
        return self._current_state.state_type

# 具象状態クラス群
class WaitingForCardState(ATMState):
    def insert_card(self, context: ATMContext, card_number: str) -> None:
        logger.info(f"Card inserted: {card_number}")
        context.card_number = card_number
        context.set_state(StateType.WAITING_FOR_PIN)
    
    def enter_pin(self, context: ATMContext, pin: str) -> None:
        logger.info("Please insert your card first.")
    
    def select_transaction(self, context: ATMContext, transaction_type: str) -> None:
        logger.info("Please insert your card first.")
    
    def confirm_transaction(self, context: ATMContext) -> None:
        logger.info("Please insert your card first.")
    
    def cancel_transaction(self, context: ATMContext) -> None:
        logger.info("No transaction to cancel.")
    
    def eject_card(self, context: ATMContext) -> None:
        logger.info("No card to eject.")
    
    @property
    def state_type(self) -> StateType:
        return StateType.WAITING_FOR_CARD
    
    @property
    def display_message(self) -> str:
        return "Please insert your card"

class WaitingForPINState(ATMState):
    def insert_card(self, context: ATMContext, card_number: str) -> None:
        logger.info("Card already inserted.")
    
    def enter_pin(self, context: ATMContext, pin: str) -> None:
        logger.info(f"PIN entered: {'*' * len(pin)}")
        if self._validate_pin(pin):
            logger.info("PIN validated successfully.")
            context.set_state(StateType.ACCOUNT_MENU)
        else:
            logger.info("Invalid PIN format.")
            context.set_state(StateType.ERROR)
    
    def _validate_pin(self, pin: str) -> bool:
        """PINの検証(簡略化)"""
        return len(pin) == 4 and pin.isdigit()
    
    def select_transaction(self, context: ATMContext, transaction_type: str) -> None:
        logger.info("Please enter your PIN first.")
    
    def confirm_transaction(self, context: ATMContext) -> None:
        logger.info("Please enter your PIN first.")
    
    def cancel_transaction(self, context: ATMContext) -> None:
        logger.info("Cancelling... Ejecting card.")
        context.set_state(StateType.WAITING_FOR_CARD)
    
    def eject_card(self, context: ATMContext) -> None:
        logger.info("Ejecting card...")
        context.set_state(StateType.WAITING_FOR_CARD)
    
    @property
    def state_type(self) -> StateType:
        return StateType.WAITING_FOR_PIN
    
    @property
    def display_message(self) -> str:
        return "Please enter your PIN"

class AccountMenuState(ATMState):
    def insert_card(self, context: ATMContext, card_number: str) -> None:
        logger.info("Card already inserted.")
    
    def enter_pin(self, context: ATMContext, pin: str) -> None:
        logger.info("Already authenticated.")
    
    def select_transaction(self, context: ATMContext, transaction_type: str) -> None:
        logger.info(f"Transaction selected: {transaction_type}")
        context.selected_transaction = transaction_type
        context.set_state(StateType.TRANSACTION_IN_PROGRESS)
    
    def confirm_transaction(self, context: ATMContext) -> None:
        logger.info("Please select a transaction first.")
    
    def cancel_transaction(self, context: ATMContext) -> None:
        logger.info("Returning to card insertion...")
        context.set_state(StateType.WAITING_FOR_CARD)
    
    def eject_card(self, context: ATMContext) -> None:
        logger.info("Thank you for using our ATM.")
        context.set_state(StateType.WAITING_FOR_CARD)
    
    @property
    def state_type(self) -> StateType:
        return StateType.ACCOUNT_MENU
    
    @property
    def display_message(self) -> str:
        return f"Account Menu - Balance: ¥1,000,000"

class TransactionInProgressState(ATMState):
    def insert_card(self, context: ATMContext, card_number: str) -> None:
        logger.info("Transaction in progress. Please wait.")
    
    def enter_pin(self, context: ATMContext, pin: str) -> None:
        logger.info("Transaction in progress. Please wait.")
    
    def select_transaction(self, context: ATMContext, transaction_type: str) -> None:
        logger.info(f"Transaction already selected: {context.selected_transaction}")
    
    def confirm_transaction(self, context: ATMContext) -> None:
        logger.info(f"Processing {context.selected_transaction}...")
        if context.selected_transaction == "Cash Withdrawal":
            context.set_state(StateType.DISPENSING_CASH)
        else:
            logger.info("Transaction completed successfully.")
            context.set_state(StateType.ACCOUNT_MENU)
    
    def cancel_transaction(self, context: ATMContext) -> None:
        logger.info("Transaction cancelled. Returning to menu.")
        context.set_state(StateType.ACCOUNT_MENU)
    
    def eject_card(self, context: ATMContext) -> None:
        logger.info("Please complete or cancel the current transaction first.")
    
    @property
    def state_type(self) -> StateType:
        return StateType.TRANSACTION_IN_PROGRESS
    
    @property
    def display_message(self) -> str:
        return "Confirm transaction: Cash Withdrawal ¥10,000"

class DispensingCashState(ATMState):
    def insert_card(self, context: ATMContext, card_number: str) -> None:
        logger.info("Please wait, dispensing cash...")
    
    def enter_pin(self, context: ATMContext, pin: str) -> None:
        logger.info("Please wait, dispensing cash...")
    
    def select_transaction(self, context: ATMContext, transaction_type: str) -> None:
        logger.info("Please wait, dispensing cash...")
    
    def confirm_transaction(self, context: ATMContext) -> None:
        logger.info("Cash dispensed. Please take your money and card.")
        context.set_state(StateType.WAITING_FOR_CARD)
    
    def cancel_transaction(self, context: ATMContext) -> None:
        logger.info("Cannot cancel while dispensing cash.")
    
    def eject_card(self, context: ATMContext) -> None:
        logger.info("Please take your cash first.")
    
    @property
    def state_type(self) -> StateType:
        return StateType.DISPENSING_CASH
    
    @property
    def display_message(self) -> str:
        return "Please take your cash"

class ErrorState(ATMState):
    def insert_card(self, context: ATMContext, card_number: str) -> None:
        logger.info("System error. Please contact support.")
    
    def enter_pin(self, context: ATMContext, pin: str) -> None:
        logger.info("System error. Resetting...")
        context.set_state(StateType.WAITING_FOR_CARD)
    
    def select_transaction(self, context: ATMContext, transaction_type: str) -> None:
        logger.info("System error. Please contact support.")
    
    def confirm_transaction(self, context: ATMContext) -> None:
        logger.info("System error. Please contact support.")
    
    def cancel_transaction(self, context: ATMContext) -> None:
        logger.info("Resetting system...")
        context.set_state(StateType.WAITING_FOR_CARD)
    
    def eject_card(self, context: ATMContext) -> None:
        logger.info("Ejecting card due to error...")
        context.set_state(StateType.WAITING_FOR_CARD)
    
    @property
    def state_type(self) -> StateType:
        return StateType.ERROR
    
    @property
    def display_message(self) -> str:
        return "System Error - Please contact support"

# 使用例とデモンストレーション
def main():
    atm = ATMContext()
    
    print("=== ATM Transaction Demo ===")
    atm.insert_card("1234-5678-9012-3456")
    atm.enter_pin("1234")
    atm.select_transaction("Cash Withdrawal")
    atm.confirm_transaction()
    atm.confirm_transaction()  # 現金受取確認
    
    print("\n=== Error Handling Demo ===")
    atm.insert_card("9876-5432-1098-7654")
    atm.enter_pin("12")  # 不正なPIN
    atm.cancel_transaction()  # エラーからの回復

if __name__ == "__main__":
    main()

パターンの応用と実践的な使用場面

Stateパターンは、以下のような場面で特に有用です:

1. ユーザーインターフェース

  • ダイアログボックスの状態管理
  • フォームのバリデーション状態
  • ゲームのメニューシステム

2. ネットワーク接続

  • TCP接続の状態管理(CLOSED, LISTEN, ESTABLISHED等)
  • HTTPリクエストの処理状態

3. ビジネスプロセス

  • 注文処理システム(受注、処理中、出荷済み等)
  • ワークフロー管理
  • 承認プロセス

4. ゲーム開発

  • キャラクターの行動状態(待機、移動、攻撃、防御等)
  • ゲーム画面の管理(メニュー、プレイ中、ポーズ等)

Stateパターンの利点と注意点

利点

  1. Single Responsibility Principle: 各状態クラスが特定の状態の振る舞いのみを担当
  2. Open/Closed Principle: 新しい状態を追加する際に既存コードを変更する必要がない
  3. 可読性の向上: 複雑なif-else文やswitch文を排除
  4. テストのしやすさ: 各状態を独立してテストできる
  5. 状態遷移の明確化: 状態遷移ロジックが明確になる

注意点

  1. クラス数の増加: 状態の数だけクラスが必要になる
  2. オーバーエンジニアリング: 単純な状態管理には過剰な場合がある
  3. 状態間の共有データ: 状態間でデータを共有する場合の設計が複雑になる可能性
  4. メモリ使用量: 各状態でオブジェクトを保持する場合のメモリ効率

代替実装パターン

Java: 列挙型を使った軽量実装

public enum MediaPlayerState {
    STOPPED {
        @Override
        public MediaPlayerState play() {
            System.out.println("Starting playback...");
            return PLAYING;
        }
        
        @Override
        public MediaPlayerState stop() {
            System.out.println("Already stopped.");
            return STOPPED;
        }
    },
    
    PLAYING {
        @Override
        public MediaPlayerState play() {
            System.out.println("Already playing.");
            return PLAYING;
        }
        
        @Override
        public MediaPlayerState stop() {
            System.out.println("Stopping playback...");
            return STOPPED;
        }
    };
    
    public abstract MediaPlayerState play();
    public abstract MediaPlayerState stop();
}

class SimpleMediaPlayer {
    private MediaPlayerState state = MediaPlayerState.STOPPED;
    
    public void play() {
        state = state.play();
    }
    
    public void stop() {
        state = state.stop();
    }
}

Python: 関数ベースの実装

# 関数ベースのState実装
class FunctionalMediaPlayer:
    def __init__(self):
        self._state = self._stopped_state
    
    def play(self):
        self._state = self._state('play')
    
    def stop(self):
        self._state = self._state('stop')
    
    def _stopped_state(self, action):
        if action == 'play':
            print("Starting playback...")
            return self._playing_state
        elif action == 'stop':
            print("Already stopped.")
            return self._stopped_state
        return self._stopped_state
    
    def _playing_state(self, action):
        if action == 'play':
            print("Already playing.")
            return self._playing_state
        elif action == 'stop':
            print("Stopping playback...")
            return self._stopped_state
        return self._playing_state

Strategy パターンとの比較

Stateパターンは、しばしばStrategyパターンと混同されますが、重要な違いがあります:

側面 State パターン Strategy パターン
目的 状態に応じた振る舞いの変更 アルゴリズムの選択と交換
状態管理 内部状態を持ち、状態遷移を管理 通常は状態を持たない
オブジェクトの役割 状態によってオブジェクトの振る舞いが変わる アルゴリズムを外部から選択
結合度 状態オブジェクト間で結合がある場合がある 戦略オブジェクト間は独立

実装の違いと特徴

特性 Java Python
型安全性 コンパイル時に状態遷移の妥当性を検証 実行時チェック、型ヒントで補完
実装方法 インターフェース、抽象クラス、列挙型 ABC、Protocol、関数ベース等多様
状態オブジェクト 通常はシングルトンまたは静的インスタンス より動的な生成と管理が可能
メモリ効率 型安全性のためのオーバーヘッド より軽量な実装が可能
拡張性 厳格な型システムによる安全な拡張 動的な特性を活かした柔軟な拡張

まとめ:本質は「状態のオブジェクト化と責任の委譲」

Stateパターンは、両言語で実装アプローチに違いがありますが、 「複雑な条件分岐を状態オブジェクトに委譲し、オブジェクトの振る舞いを状態に応じて動的に変更する」 という本質は共通です。

Javaは厳格な型システムと明確なインターフェース定義により、安全で保守しやすい状態管理を実現します。一方、Pythonは言語の柔軟性を活かして、より軽量で多様な実装アプローチを提供します。

このパターンは、特に以下の条件が揃った場合に威力を発揮します:

  • 複数の状態: 3つ以上の明確に区別される状態がある
  • 状態固有の振る舞い: 各状態で異なる処理が必要
  • 状態遷移: 状態間の遷移ルールが存在する
  • 拡張性: 将来的に新しい状態が追加される可能性がある

現代のソフトウェア開発において、ユーザーインターフェース、ワークフロー、ゲーム開発など様々な分野で重要な役割を果たしており、特に複雑な状態管理が必要なシステムでは必須のパターンといえるでしょう。

明日は、振る舞いパターンの中でも特に重要な、Strategyパターンについて解説します。お楽しみに!

次回のテーマは、「Day 23 Strategyパターン:アルゴリズムを動的に切り替える」です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?