The State pattern lets an object to change its behavior at run-time by changing its internal state.
The State pattern defines objects, which represent different states. It also defines an object that uses these states and changes behavior by swapping the state objects.
When the State pattern is used, several classes have to be created. This is a drawback of this pattern.
State is a behavioral pattern.
In the next Java example, we are going to use an imaginary gun, which have different states like magazine empty or removed and safety can be on or off. When its trigger is pulled, obviously it has different behavior depending whether it is loaded or not and so on.
The State
interface defines the methods those can be used for operating the gun.
package com.programcodex.designpatterns.state;
public interface State {
void ejectMagazine();
void pullTrigger();
void switchSafetyOn();
void switchSafetyOff();
}
Next, we are creating four states.
The first is the MagazineRemovedState
, and it implements the State
interface.
It defines how the gun works in this state.
What will happen when we try to remove the magazine when it has already been removed?
Or what will happen when we pull the trigger without any magazine?
Every state class has a Gun
field.
In this state, it does nothing, but it will be used in other states.
package com.programcodex.designpatterns.state;
public class MagazineRemovedState implements State {
private final Gun gun;
public MagazineRemovedState(Gun gun) {
this.gun = gun;
}
@Override
public void ejectMagazine() {
System.out.println("Magazine cannot be ejected because it has already been removed.");
}
@Override
public void pullTrigger() {
System.out.println("The trigger was pulled, but there is no magazine.");
}
@Override
public void switchSafetyOn() {
System.out.println("The safety was switched on, but there is no magazine.");
}
@Override
public void switchSafetyOff() {
System.out.println("The safety was switched off, but there is no magazine.");
}
}
The gun gets into the SafetyOnState
when it gets loaded.
So in this state, the magazine can be ejected by the ejectMagazine
method.
When this happens, the gun gets into MagazineRemovedState
also by this method.
The switchSafetyOff
method prepares the gun for shooting and
sets it into switchSafetyOff
state.
Of course, when safety is on and the trigger is pulled, nothing happens. Also the safety cannot be activated again from this state.
package com.programcodex.designpatterns.state;
public class SafetyOnState implements State {
private final Gun gun;
public SafetyOnState(Gun gun) {
this.gun = gun;
}
@Override
public void ejectMagazine() {
System.out.println("Magazine is ejected.");
gun.setState(gun.getMagazineRemovedState());
}
@Override
public void pullTrigger() {
System.out.println("The trigger was pulled,"
+ " but nothing happens while the safety is on.");
}
@Override
public void switchSafetyOn() {
System.out.println("Safety was already on, so nothing happens.");
}
@Override
public void switchSafetyOff() {
System.out.println("Safety is off now.");
gun.setState(gun.getSafetyOffState());
}
}
From the safety on state, our imaginary gun can get into the safety off state.
In this state, the pullTrigger
method has the most interesting behavior
because this time the gun actually fires.
When a bullet leaves, the bullet counter is decremented.
If the gun runs out of bullets, its state becomes MagazineEmptyState
.
package com.programcodex.designpatterns.state;
public class SafetyOffState implements State {
private final Gun gun;
public SafetyOffState(Gun gun) {
this.gun = gun;
}
@Override
public void ejectMagazine() {
System.out.println("Magazine cannot be ejected while the safty is off.");
}
@Override
public void pullTrigger() {
gun.decrementBulletCounter();
System.out.format("FIRE! %d bullet%s left.%n", gun.getBulletCounter(),
gun.getBulletCounter() > 1 ? "s" : "");
if (gun.getBulletCounter() == 0) {
gun.setState(gun.getMagazineEmptyState());
}
}
@Override
public void switchSafetyOn() {
System.out.println("Safety is on now.");
gun.setState(gun.getSafetyOnState());
}
@Override
public void switchSafetyOff() {
System.out.println("Safety is already off, so nothing happens.");
}
}
When the gun runs out of bullets, its state becomes MagazineEmptyState
.
In this state, the only thing what we can do is ejecting the magazine.
The ejectMagazine
method also sets the gun's state to MagazineRemovedState
.
In this state, the other methods do nothing useful.
package com.programcodex.designpatterns.state;
public class MagazineEmptyState implements State {
private final Gun gun;
public MagazineEmptyState(Gun gun) {
this.gun = gun;
}
@Override
public void ejectMagazine() {
System.out.println("The magazine is ejected.");
gun.setState(gun.getMagazineRemovedState());
}
@Override
public void pullTrigger() {
System.out.println("The trigger was pulled, but there is no bullet.");
}
@Override
public void switchSafetyOn() {
System.out.println("The safety was switched on, but there is no bullet.");
}
@Override
public void switchSafetyOff() {
System.out.println("The safety was switched off, but there is no bullet.");
}
}
All of the possible states are implemented. It’s time to create the Gun
class.
This class contains instances from each state. These are stored in the first four fields.
The state
field contains the current state.
At the beginning, it holds the magazineRemovedState
.
Every time when the gun’s state is changing, one of the four state gets assigned to this field.
The bulletCounter
represents the bullets in the magazine.
The Gun
class has all the methods those found in the State
interface,
but the gun is not a state, therefore it does not implement that.
In a more complex example, these method names and their numbers not necessarily meet.
These methods delegate the operations to the current state object,
so the gun works according to its internal state.
After those, we can found get methods, which are called from the state objects.
The last methods deals with the bullets.
The insertMagazineWithBullets
loads the bullets into the gun and
sets the state into SafetyOnState
.
package com.programcodex.designpatterns.state;
public class Gun {
private final State magazineRemovedState = new MagazineRemovedState(this);
private final State magazineEmptyState = new MagazineEmptyState(this);
private final State safetyOnState = new SafetyOnState(this);
private final State safetyOffState = new SafetyOffState(this);
private State state = magazineRemovedState;
private int bulletCounter;
public void ejectMagazine() {
state.ejectMagazine();
this.bulletCounter = 0;
}
public void pullTrigger() {
state.pullTrigger();
}
public void switchSafetyOn() {
state.switchSafetyOn();
}
public void switchSafetyOff() {
state.switchSafetyOff();
}
public void setState(State state) {
this.state = state;
}
public State getMagazineRemovedState() {
return magazineRemovedState;
}
public State getMagazineEmptyState() {
return magazineEmptyState;
}
public State getSafetyOnState() {
return safetyOnState;
}
public State getSafetyOffState() {
return safetyOffState;
}
public void insertMagazineWithBullets(int bullets) {
System.out.format("Magazine is inserted with %d bullets.%n",
bullets);
this.bulletCounter = bullets;
setState(getSafetyOnState());
}
public int getBulletCounter() {
return bulletCounter;
}
public void decrementBulletCounter() {
if (bulletCounter > 0) {
bulletCounter--;
}
}
}
And we are done. It’s time to run these things.
First of all, an instance is created from the Gun
class that can have different states.
It is used by the useGun
method in each state.
Using the gun means pulling its trigger while the safety is on,
then switching the safety and pulling the trigger three times, finally switching the safety back.
At first, the gun is used without any magazine. After that, a magazine with two bullets is inserted and used again. In the third try, the magazine is ejected. In the last case, a magazine with ten bullets is inserted.
The gun behaves differently depending on its internal state.
package com.programcodex.designpatterns.state;
public class TestState {
public static void main(String[] args) {
Gun gun = new Gun();
useGun(gun);
gun.insertMagazineWithBullets(2);
useGun(gun);
gun.ejectMagazine();
useGun(gun);
gun.insertMagazineWithBullets(10);
useGun(gun);
}
private static void useGun(Gun gun) {
System.out.println();
gun.pullTrigger();
gun.switchSafetyOff();
gun.pullTrigger();
gun.pullTrigger();
gun.pullTrigger();
gun.switchSafetyOn();
System.out.println();
}
}
The output of the above test class: