Moe's Tech Blog

[Design Pattern] State Design Pattern 본문

Software Design Pattern/Notes

[Design Pattern] State Design Pattern

moe12825 2022. 8. 26. 01:41

Motivation

  • Let's say instructor asks you to do a dance but remain in the same state (sitting, standing, laying down)
    • If you are sitting down, your dance might involve you waving your arms and shoulders and bobbing head
    • If you are standing, you would be able to add some foot works to your dance
    • If you are laying down, you might throw your hands in the air and wiggle your body
  • All the instructor told was dance, and we chose a dance that was applicable to your current state

 

State Design Pattern

  • What is it?
    • has objects in your current code aware of the current state
    • objects can choose an appropriate behavior based on their current state
    • when this current state changes, this behavior can be altered
  • When should you use it?
    • Is primarily used when you need to change the behavior of an object based upon the state that it's in at run-time
  • Example
    • 1. Vending Machine
      • A vending machine represents a state pattern well since it has several states and specific actions are based on those states
      • 1. Say I want to extract chocolates from the vending machine
        • The chocolate bar costs $1
        • So, the person walks up to the vending machine and makes the selection
      • 2. Say if the person doesn't want the chocolate bar anymore
        • The person would select "eject" button and the machine would return the dollar
      • 3. Say the vending machine runs out of chocolate bars
        • The vending machine needs to be keeping track of it's inventory and notifying the customers when there is no more of a particular product left

 

State Design Pattern UML Diagram

  • There are three states in the vending machine state UML diagram
    • 1. Idle
    • 2. Has One Dollar
    • 3. Out of Stock
    • The vending machine is in one state at a time
  • You can also see that there are three triggers or events:
    • 1. Insert Dollar
    • 2. Eject Money
    • 3. Dispense\
  • There are two actions the vending machine can do:
    • 1. doReturnMoney
    • 2. doReleaseProduct
  • In code, the vending machine class looks like this:
public class VendingMachine {
    public VendingMachine(int count) {

    }

    // handle user events
    public void insertDollar() {

    }

    public void ejectMoney() {

    }

    public void dispense() {
        
    }
}
  • Think about states:
    • 1. Before approachine the vending machine, the vending machine is in an idle state, and nothing is happening
    • 2. When inserting my dollar, the state of the vending machine changes, and there are events to respond and actions to perform that are now relevant to the vending machine
      • They would be
        • 1. responding to an ejecting money reuqest by returning my money
        • 2. responding to a dispense request by releasing a product
        • 3. responding to out of stock by returning money

 

How to Create State Design Pattern

  • NOT the correct way
  • 1. Create a singleton state objects
    • The three states are defined as static variables
public final class State {
    private State() {} 

    // all potential vending machine state as singletons
    public final static State Idle = new State();
    public final static State HasOneDollar = new State();
    public final static State OutOfStock = new State();   
}
  • 2. Create vending machine class
public class VendingMachine {
    private State currentState;
    private int count;

    public VendingMachine(int count) {
        if (count > 0) {
            this.currentState = State.Idle;
            this.count = count;
        } else {
            this.currentState = State.OutOfStock;
            this.count = 0;
        }
    }

    // handle insert dollar trigger
    public void insertDollar() {
        if (this.currentState = State.Idle) {
            this.currentState = State.HasOneDollar;
        }  else if (this.currentState == State.HasOneDollar) {
            this.doReturnMoney();
            this.currentState = State.Idle;
        } else if (this.currentState == State.OutOfStock) {
            this.doReturnMoney();
        }
    }
}
  • Correct Way
  • when a request is asked of a context object, it delegates to state object to actually handle the request
  • The objects above are passive and  doesn't have much responsibility themselves
    • Let's see how this can be re-structured using state design pattern

 

  • Step 1: Define a state interface with a method for each trigger that a state needs to respond to
public interface State {
    public void insertDollar( VendingMachine vendingMachine);
    public void ejectMoney(VendingMachine vendingMachine);
    public void dispense(VendingMachine vendingMachine);
}
  • Step 2: Define idle state
public class IdleState implements State {
    public void insertDollar(VendingMachine vendingMachine) {
        System.out.println("Dollar Inserted");

        vendingMachine.setState(
            vendingMachine.getHasOneDollarState();
        )
    } 

    public void ejectMoney( VendingMachine vendingMachine ) {
        System.out.println("No money to return");
    }

    public void dispense(VendingMachine vendineMachine) {
        System.out.println("Payment required");
    }
}
public class HasOneDollarState implements State {
    public void insertDollar (VendingMachine vendingMachine) {
        System.out.println("Already have one dollar");

        vendingMachine.doReturnMoney();
        vendingMachine.setState(
            vendingMachine.getIdleState()
        );
    }

    public void ejectMoney(VendingMachine vendingMachine) {
        System.out.println("Returning money");

        vendingMachine.doReturnMoney();
        vendingMachine.setState(
            vendingMachine.getIdleState();
        );
    }

    public void dispense(VendingMachine vendingMachine) {
        System.out.println("Releasing product");

        if (vendingMachine.getCount() > 1) {
            vendingMachine.doReleaseProduct();
            vendingMachine.setState(
                vendingMachine.getIdleState();
            );
        } else {
            vendingMachine.doReleaseProduct();
            vendingMachine.setState(
                vendingMachine.getOutOfStockState();
            )
        }
    }
}
  • VendingMachine class
    • is the Context class in above UML diagram
    • is now delegates handling to the current state object
public class VendingMachine {
    private State idleState;
    private State hasOneDollarState;
    private State outOfStockState;

    private State currentState;
    private int count;

    public VendingMachine (int count) {
        // make the needed states
        this.idleState = new IdleState();
        this.hasOneDollarState = new HasOneDollarState();
        this.outOfStockState = new outOfStockState();

        if (count > 0) {
            this.currentState = this.idleState
            this.count = count;
        } else {
            this.currentState = this.outOfStockState;
            this.count = 0;
        }
    }

    public void insertDollar() {
        currentState.insertDollar(this);
    }

    public void ejectMoney() {
        currentState.ejectMoney(this);
    }

    public void dispense() {
        currentState.dispense(this);
    }
}

 

Summary

  • State pattern is useful when you need to change the behavior of an object based upon changes to its internal state
  • State pattern can also be used to simplify methods with long conditionals that depend on the object state