definition
State mode: when the internal state of an object changes, it is allowed to change its behavior. The object looks like it has changed its class.
And strategy mode are twins.
It mainly solves the problem when the conditional expression controlling the state transition of an object is too complex. That is, the state judgment logic is transferred to a series of classes that identify different states.
Applicable scenario
- A scene in which behavior changes with state changes. For example, if a designer performs the same behavior with different permissions, the results will be different.
- Replace the condition and branch statement, and realize the condition judgment processing by extending the subclass..
Main role
- Context: the object containing the state. It can process some requests. The final response of these requests will be related to the state.
- State: it defines the behavior set of each state, which will be used in the Context.
- Concrete state: a concrete state class that implements related behaviors
State can use interfaces or abstract classes. Using abstract classes can more easily extend new methods.
give an example
Candy selling machine from Head FIRST design pattern.
Realize a candy machine, put in two coins, you can spit out a sugar, during which you can refund the coin.
Therefore, the candy machine has the following states: no coins, coins, candy sold, candy sold out. If you use logical control statements to realize this function, you need a large number of if else to control, and it is difficult to expand. In the future, you need to change all control statements to add a new state.
First let's create a State interface
interface State{ void insertQuarter(); void ejectQuarter(); void turnCrank(); void dispense(); }
Then, each state in the design is encapsulated into a class, and each implements the state interface. Take NoQuarterState as an example:
class NoQuarterState implements State{ GumballMachine gumballMachine; public NoQuarterState(GumballMachine gumballMachine){ this.gumballMachine=gumballMachine; } @Override public void insertQuarter() { System.out.println("If someone invested 25 cents, we printed a message saying we accepted 25 cents," + "Then change the state of the machine to HasQuarterState"); gumballMachine.setState(gumballMachine.getHasQuarterState()); } @Override public void ejectQuarter() { System.out.println("If you don't give the money, you don't ask for a refund"); } @Override public void turnCrank() { System.out.println("If you don't give money, you can't ask for candy"); } @Override public void dispense() { System.out.println("If we don't get the money, we can't distribute candy."); } }
Implement the context class,
class GumballMachine { //Define status State soldOutState; State noQuarterState; State hasQuarterState; State soldState; //current state State state = soldState; //Candy margin int count = 0; public GumballMachine_(int count) { //Will own soldOutState = new SoldOutState(this); noQuarterState = new NoQuarterState(this); hasQuarterState = new HasQuarterState(this); soldOutState = new SoldState(this); this.count = count; if (count > 0) { state = noQuarterState; } } //Put in a coin public void insertQuarter() { state.insertQuarter(); } //Refund public void ejectQuarter() { state.ejectQuarter(); } //Turn the handle public void turnCrank(){ state.turnCrank(); state.dispense(); } //Set current state void setState(State state){ this.state=state; } //Release candy void releaseBall(){ System.out.println(); if(count!=0){ count=count-1; } } public State getSoldState() { return soldState; } public State getHasQuarterState() { return hasQuarterState; } public State getNoQuarterState() { return noQuarterState; } public State getSoldOutState() { return soldOutState; } }
Similarly, continue to implement other state classes
If you want to add a new status: one out of ten times, you only need to make the following changes:
First, add a state to the GumballMahine class
class GumballMachine { //New definition status State winnerState
Implement this winnerState:
class WinnerState implements State{ GumballMachine_ gumballMachine; public WinnerState(GumballMachine_ gumballMachine) { this.gumballMachine = gumballMachine; } @Override public void insertQuarter() { System.out.println("Inappropriate action"); } @Override public void ejectQuarter() { System.out.println("Inappropriate action"); } @Override public void turnCrank() { System.out.println("Inappropriate action"); } @Override public void dispense() { //If there's a second candy, we'll release it. gumballMachine.releaseBall(); if(gumballMachine.getCount()==0){ gumballMachine.setState(gumballMachine.getSoldState()); }else{ gumballMachine.releaseBall(); if(gumballMachine.getCount()>0){ gumballMachine.setState(gumballMachine.getNoQuarterState()); }else{ gumballMachine.setState(gumballMachine.getSoldOutState()); } } } }
Next, you only need to add an entry into winnerstate in HasQuarterState.
class HasQuarterState implements State { //Generate random number Random random = new Random(System.currentTimeMillis()); GumballMachine_ gumballMachine; public HasQuarterState(GumballMachine_ gumballMachine) { this.gumballMachine = gumballMachine; } @Override public void insertQuarter() { System.out.println("This is an inappropriate action for this state"); } @Override public void ejectQuarter() { System.out.println("Withdraw the customer's 25 cents and switch the status to NoQuarterState state"); gumballMachine.setState(gumballMachine.getNoQuarterState()); } @Override public void turnCrank() { System.out.println("When the crank turns, we change the state to SoldState"); int winner = random.nextInt(10); //One tenth probability of entering winner mode if ((winner == 0) && (gumballMachine.getCount() > 1)) { gumballMachine.setState(gumballMachine.getWinnerState()); } else { gumballMachine.setState(gumballMachine.getSoldState()); } } @Override public void dispense() { System.out.println("This is another inappropriate action of this state"); } }
be careful
The environment class (candy machine) of the above code can only have one instance, because the state object holds the environment class. If you want to share the state object, you can set the state object to static and pass in the reference of Context in handler(). For example, refer to the third blog: (21) Detailed explanation of status mode (DOTA version) Examples in.
Relationship with policy pattern
Strategy mode
As like as two peas can be found, the class diagram is similar to the policy pattern, but the difference between the two modes is their intention: the policy pattern usually configures the Context class by actions or algorithms (the client does not need to know the change of Contex); the state mode allows Context to change behavior with the change of state.
Advantages and disadvantages
advantage
- The structure is clear, the complexity of the program is avoided, and the maintainability of the system is improved
- It embodies the principle of opening and closing and the principle of single responsibility. Each state is a subclass, which is highly consistent with the principle of single responsibility. Expanding the state only needs to add subclasses, which is the embodiment of the principle of opening and closing. Encapsulation is also very consistent with Demeter's law
- The customer will not directly interact with the state. The customer only needs to put in coins, turn the handle and other actions, and does not need to know about the machine and others
shortcoming
If a transaction has many states, it will cause too many subclasses. It is necessary to measure whether the state pattern is used when the project is used
reference resources
On Design Pattern -- state pattern
State mode of Head First design mode
(21) Detailed explanation of status mode (DOTA version)