Moe's Tech Blog
[Design Pattern] State Design Pattern 본문
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
- 1. Vending Machine
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
- They would be
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
'Software Design Pattern > Notes' 카테고리의 다른 글
[Design Pattern] Command Pattern (0) | 2022.08.27 |
---|---|
[Design Pattern] Chain of Responsibility Pattern (0) | 2022.08.25 |
[Design Pattern] Template Design Pattern (0) | 2022.08.24 |
[Design Pattern] Decorator Pattern (0) | 2022.08.22 |
Summary of Design Patterns (0) | 2022.08.22 |