Skip to content

Latest commit

 

History

History
159 lines (117 loc) · 7.05 KB

File metadata and controls

159 lines (117 loc) · 7.05 KB

State Pattern

Overview

The State Pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class.

Pattern Structure

The State Pattern encapsulates state-based behavior and delegates behavior to the current state. This example demonstrates a gumball machine with different states and transitions.

Key Concepts

  • Context: The GumballMachine class that maintains a reference to the current state
  • States: Different states the machine can be in (SOLD_OUT, NO_QUARTER, HAS_QUARTER, SOLD)
  • State Transitions: Actions (insertQuarter, ejectQuarter, turnCrank, dispense) cause transitions between states
  • Encapsulated Behavior: Each state handles actions differently based on the current state

Implementation Details

This implementation shows a gumball machine with four states:

  1. SOLD_OUT - No gumballs available
  2. NO_QUARTER - Ready to accept a quarter
  3. HAS_QUARTER - Quarter inserted, ready to dispense
  4. SOLD - Currently dispensing a gumball

The machine handles four main actions:

  • insertQuarter() - Insert a quarter into the machine
  • ejectQuarter() - Request a refund of the quarter
  • turnCrank() - Turn the crank to get a gumball
  • dispense() - Internal method to dispense a gumball

State Diagram

                      ┌──────────────┐
                      │   SOLD_OUT   │
                      └──────┬───────┘
                             │ refill()
                             │ [count > 0]
                             ▼
                      ┌──────────────┐
           ┌─────────▶│  NO_QUARTER  │◀─────────┐
           │          └──────┬───────┘          │
           │                 │                   │
           │                 │ insertQuarter()   │
           │                 │                   │
           │                 ▼                   │
           │          ┌──────────────┐          │
           │          │ HAS_QUARTER  │          │
           │          └──┬────────┬──┘          │
           │             │        │              │
           │  ejectQuarter()   turnCrank()       │
           │             │        │              │
           │             │        ▼              │
           │             │   ┌──────────┐       │
           │             │   │   SOLD   │       │
           │             │   └─────┬────┘       │
           │             │         │             │
           │             │     dispense()        │
           │             │         │             │
           │             │    [count > 0]        │
           └─────────────┴─────────┴─────────────┘
                                   │
                              [count == 0]
                                   │
                                   ▼
                            ┌──────────────┐
                            │   SOLD_OUT   │
                            └──────────────┘

State Transitions

  • SOLD_OUT → NO_QUARTER: When machine is refilled with gumballs
  • NO_QUARTER → HAS_QUARTER: When a quarter is inserted
  • HAS_QUARTER → NO_QUARTER: When quarter is ejected
  • HAS_QUARTER → SOLD: When crank is turned
  • SOLD → NO_QUARTER: After dispensing, if gumballs remain
  • SOLD → SOLD_OUT: After dispensing the last gumball

Files

How to Compile

From the parent directory of StatePattern:

javac -d out StatePattern/FirstTry/*.java

How to Run

java -cp out StatePattern.FirstTry.GumballMachineTestDrive

Expected Output

The test drive will demonstrate various scenarios:

  1. Successfully purchasing a gumball
  2. Inserting and ejecting a quarter
  3. Attempting to turn crank without a quarter
  4. Multiple purchase attempts
  5. Machine running out of gumballs

Limitations of This Implementation

Note: This implementation uses integer constants and conditional statements (if-else) to manage states, which does not fully leverage the State Pattern's benefits. This approach has significant drawbacks:

Problems with the Current Design

  1. Difficult to Extend: Adding a new state (like a WINNER state) requires modifying multiple methods throughout the GumballMachine class
  2. Violates Open/Closed Principle: You must modify existing code rather than extending it
  3. Scattered State Logic: State-specific behavior is spread across multiple if-else blocks instead of being encapsulated

Example: Adding a Winner State

Consider adding a WINNER state where 10% of crank turns dispense 2 gumballs instead of 1. With the current implementation, you would need to:

  1. Add a new constant: final static int WINNER = 4;
  2. Modify turnCrank() to randomly transition to WINNER instead of SOLD
  3. Modify dispense() to handle the WINNER state (dispense 2 gumballs)
  4. Update all other methods (insertQuarter(), ejectQuarter(), toString()) to handle the WINNER case
  5. Ensure proper state transitions from WINNER back to NO_QUARTER or SOLD_OUT

This requires changes across the entire class, making the code fragile and error-prone.

Better Approach: Using State Objects

A proper State Pattern implementation would use separate state classes (NoQuarterState, HasQuarterState, SoldState, SoldOutState, WinnerState) where:

  • Each state class encapsulates its own behavior
  • Adding a new state only requires creating a new state class
  • The context (GumballMachine) simply delegates to the current state object
  • No modifications to existing state classes are needed

This implementation serves as a "before" example showing why the State Pattern with separate state classes is beneficial.

Benefits of the State Pattern (When Properly Implemented)

  • Eliminates conditional complexity: Removes large if-else or switch statements
  • Encapsulates state-specific behavior: Each state's behavior is localized
  • Makes state transitions explicit: Clear transitions between states
  • Easy to add new states: Simply add new state classes without modifying existing code
  • Open/Closed Principle: Open for extension, closed for modification

When to Use

Use the State Pattern when:

  • An object's behavior depends on its state and must change at runtime
  • Operations have large conditional statements that depend on the object's state
  • State-specific behavior needs to be encapsulated and made interchangeable