[Fifteen] Design Patterns ~~~ Behavioral Patterns ~~~ State Patterns (Java)

[Learning Difficulty: ★★★☆☆, Frequency of Use: ★★★☆☆】

4.1. Pattern Motivation

  • In many cases, the behavior of an object depends on one or more dynamically changing properties. Such properties are called states, and such objects are called stateful objects. extracted from the value. When such an object interacts with external events, its internal state changes, causing the system's behavior to change accordingly.
  • State diagrams can be used in UML to describe changes in the state of objects.

Case 1

      "People have sorrows and joys, and the moon has cloudy and sunny waxes and wanes." Many things, including people, have multiple states, and they will have different behaviors in different states, and these states will also transform into each other under certain conditions. Just like water, it can freeze into ice, or it can be heated and evaporated into water vapor, water can flow, ice can be carved, and steam can diffuse. We can use the UML state diagram to describe the three states of H2O, as shown in Figure 1:
insert image description here

      In a software system, some objects have multiple states like water, and these states can be converted to each other under certain circumstances, and the objects will also have different behaviors in different states. In order to better design these objects with multiple states, we can use a design pattern called the state pattern,

Case 2 (account class design in the banking system)

      Sunny Software Company intends to develop a credit card business system for a bank. Bank account (Account) is one of the core classes of the system. Through analysis, the developers of Sunny Software Company found that there are three states of accounts in the system, and in There are different behaviors for accounts in different states, and the details are as follows:

  • (1) If the balance in the account is greater than or equal to 0, the state of the account is the normal state (Normal State), at this time the user can either deposit or withdraw from the account;
  • (2) If the balance in the account is less than 0 and greater than -2000, the state of the account is an overdraft state (Overdraft State). At this time, the user can either deposit or withdraw from the account, but the interest needs to be calculated on a daily basis ;
  • (3) If the balance in the account is equal to -2000, then the state of the account is Restricted State (Restricted State). At this time, the user can only deposit to the account and cannot withdraw from it, and the interest will also be calculated on a daily basis;
  • (4) Depending on the balance, the above three states can be converted to each other.

      The developers of Sunny Software Company analyzed the bank account class and drew the UML state diagram shown in Figure 2:
insert image description here

      In Figure 2, NormalState represents the normal state, OverdraftState represents the overdraft state, and RestrictedState represents the restricted state. In these three states, the account object has different behaviors. The method deposit() is used for deposits, withdraw() is used for withdrawals, and computeInterest () is used to calculate interest, and stateCheck() is used to judge whether to perform state transition and implement state transition according to the balance after each deposit and withdrawal operation. The same method may have different implementations in different states. In order to realize the various behaviors of objects in different states and the mutual conversion between object states, the developers of Sunny Software Company designed a relatively large account class Account, and some of the codes are as follows:

class Account {
    
    
	private String state; //状态
	private int balance; //余额
	......
	
	//存款操作	
	public void deposit() {
    
    
		//存款
		stateCheck();	
	}
	
	//取款操作
	public void withdraw() {
    
    
		if (state.equalsIgnoreCase("NormalState") || state.equalsIgnoreCase("OverdraftState ")) {
    
    
			//取款
			stateCheck();
		}
		else {
    
    
			//取款受限
		}
	}
	
	//计算利息操作
	public void computeInterest() {
    
    
		if(state.equalsIgnoreCase("OverdraftState") || state.equalsIgnoreCase("RestrictedState ")) {
    
    
			//计算利息
		}
	}
	
	//状态检查和转换操作
	public void stateCheck() {
    
    
		if (balance >= 0) {
    
    
			state = "NormalState";
		}
		else if (balance > -2000 && balance < 0) {
    
    
			state = "OverdraftState";
		}
		else if (balance == -2000) {
    
    
			state = "RestrictedState";
		}
        else if (balance < -2000) {
    
    
            //操作受限
        }
	}
	......
}

      Analyzing the above code, we can easily find the following problems:

  • (1) Almost every method contains a state judgment statement to judge whether the method exists in the state and how to implement the method in a specific state, resulting in very lengthy code and poor maintainability;
  • (2) It has a relatively complex stateCheck() method, which contains a large number of if...else if...else... statements for state transitions. The code test is difficult and not easy to maintain;
  • (3) The scalability of the system is poor. If a new state needs to be added, such as the frozen state (Frozen State, in which neither deposit nor withdrawal is allowed), the original code needs to be modified a lot to expand very troublesome.

      In order to solve these problems, we can use the state mode. In the state mode, we encapsulate the behavior of the object in each state and the state transition statement in each state class, and use these state classes to disperse the lengthy conditional transition statements. To make the system more flexible and scalable, the state model can solve the above problems to a certain extent.

4.2. Schema Definition

      State Pattern: Allows an object to change its behavior when its internal state changes, the object appears to modify its class. Its alias is Objects for States, and the state mode is an object behavior mode

4.3. Schema structure

The state pattern contains the following roles:

  • Context: Environment class , also known as context class, is an object with multiple states. Due to the diversity of the state of the environment class and the behavior of the object in different states, the state is separated to form a separate state class. An instance of the abstract state class State is maintained in the environment class, and this instance defines the current state. In the concrete implementation, it is an object of a State subclass.
  • State: Abstract state class , which is used to define an interface to encapsulate behaviors related to a specific state of the environment class. The methods corresponding to various states are declared in the abstract state class, and these methods are implemented in its subclasses , since the behavior of objects in different states may be different, the implementation of methods in different subclasses may be different, and the same method can be written in the abstract state class.
  • ConcreteState: Concrete state class , which is a subclass of the abstract state class, each subclass implements a behavior related to a state of the environment class, each concrete state class corresponds to a specific state of the environment, different concrete state classes have different behaviors different.
    insert image description here

      In state mode, we encapsulate the behavior of objects in different states into different state classes. In order to make the system more flexible and scalable, and at the same time encapsulate the common behaviors in each state, we need to The state is abstracted, and the abstract state class role is introduced. The typical code is as follows:

abstract class State {
    
    
    //声明抽象业务方法,不同的具体状态类可以不同的实现
	public abstract void handle();
}

      The business method declared in the abstract state class is implemented in the subclass of the abstract state class, that is, the concrete state class. Different concrete state classes can provide completely different method implementations. In actual use, a state class may contain multiple If the implementation of some business methods in the specific state class is exactly the same, these methods can be moved to the abstract state class to realize code reuse. The typical code of the specific state class is as follows:

class ConcreteState extends State {
    
    
	public void handle() {
    
    
		//方法具体实现代码
	}
}

      The environment class maintains a reference to the abstract state class. Through the setState() method, different state objects can be injected into the environment class, and then the method of the state object is called in the business method of the environment class. The typical code is as follows:

class Context {
    
    
	private State state; //维持一个对抽象状态对象的引用
	private int value; //其他属性值,该属性值的变化可能会导致对象状态发生变化
 
    //设置状态对象
	public void setState(State state) {
    
    
		this.state = state;
	}
 
	public void request() {
    
    
		//其他代码
		state.handle(); //调用状态对象的业务方法
		//其他代码
	}
}

      The environment class is actually an object that really has a state. We just extract the code related to the state in the environment class and encapsulate it into a special state class. In the state mode structure diagram, there is a one-way association relationship between the environment class Context and the abstract state class State, and a State object is defined in Context. In actual use, there may be more complex relationships between them, and there may also be dependencies or associations between State and Context.
      In the process of using the state mode, the states of an object can also be converted to each other. There are usually two ways to realize the state conversion: (1) The environment class is responsible
      for the transition between states . At this time, the environment class It also acts as a state manager (State Manager). In the business method of the environment class, the state transition is realized by judging some attribute values. It can also provide a special method for realizing attribute judgment and state transition, as shown in the following code snippet Shown:


      public void changeState() {
    
    
		//判断属性值,根据属性值进行状态转换
      if (value == 0) {
    
    
			this.setState(new ConcreteStateA());
		}
		else if (value == 1) {
    
    
			this.setState(new ConcreteStateB());
		}

	}

      (2) The specific state class is responsible for the transition between states . You can judge some attribute values ​​of the environment class in the business method of the specific state class, and then set a new state object for the environment class according to the situation to realize the state transition. Similarly, It is also possible to provide a special method to be responsible for judging attribute values ​​and state transitions. At this point, there will be a dependency or association relationship between the state class and the environment class, because the state class needs to access the attribute values ​​​​in the environment class, as shown in the following code snippet:

	……
      public void changeState(Context ctx) {
    
    
		//根据环境对象中的属性值进行状态转换
      if (ctx.getValue() == 1) {
    
    
			ctx.setState(new ConcreteStateB());
		}
		else if (ctx.getValue() == 2) {
    
    
			ctx.setState(new ConcreteStateC());
		}
        ......
	}
    ……

4.4. Timing diagram

insert image description here

4.5. Code Analysis

      The developers of Sunny Software Company use the state mode to solve the conversion problem of the account state. The client only needs to perform simple deposit and withdrawal operations, and the system will automatically switch to the corresponding state according to the balance. Its basic structure is shown in Figure 4:
insert image description here

4.5.1 Production

      In Figure 4, Account acts as an environment class role, AccountState acts as an abstract state role, and NormalState, OverdraftState, and RestrictedState act as a concrete state role. The full code is as follows:

package com.zyz.demo;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/5/28 21:50
 * @Description:
 */


//银行账户:环境类

class Account {
    
    
    private AccountState state; //维持一个对抽象状态对象的引用
    private String owner; //开户名
    private double balance = 0; //账户余额

    public Account(String owner,double init) {
    
    
        this.owner = owner;
        this.balance = balance;
        this.state = new NormalState(this); //设置初始状态
        System.out.println(this.owner + "开户,初始金额为" + init);
        System.out.println("---------------------------------------------");
    }

    public double getBalance() {
    
    
        return this.balance;
    }

    public void setBalance(double balance) {
    
    
        this.balance = balance;
    }

    public void setState(AccountState state) {
    
    
        this.state = state;
    }

    public void deposit(double amount) {
    
    
        System.out.println(this.owner + "存款" + amount);
        state.deposit(amount); //调用状态对象的deposit()方法
        System.out.println("现在余额为"+ this.balance);
        System.out.println("现在帐户状态为"+ this.state.getClass().getName());
        System.out.println("---------------------------------------------");
    }

    public void withdraw(double amount) {
    
    
        System.out.println(this.owner + "取款" + amount);
        state.withdraw(amount); //调用状态对象的withdraw()方法
        System.out.println("现在余额为"+ this.balance);
        System.out.println("现在帐户状态为"+ this. state.getClass().getName());
        System.out.println("---------------------------------------------");
    }

    public void computeInterest()
    {
    
    
        state.computeInterest(); //调用状态对象的computeInterest()方法
    }
}


//抽象状态类

abstract class AccountState {
    
    
    protected Account acc;
    public abstract void deposit(double amount);
    public abstract void withdraw(double amount);
    public abstract void computeInterest();
    public abstract void stateCheck();
}


//正常状态:具体状态类

class NormalState extends AccountState {
    
    
    public NormalState(Account acc) {
    
    
        this.acc = acc;
    }

    public NormalState(AccountState state) {
    
    
        this.acc = state.acc;
    }

    @Override
    public void deposit(double amount) {
    
    
        acc.setBalance(acc.getBalance() + amount);
        stateCheck();
    }
    @Override
    public void withdraw(double amount) {
    
    
        acc.setBalance(acc.getBalance() - amount);
        stateCheck();
    }

    @Override
    public void computeInterest()
    {
    
    
        System.out.println("正常状态,无须支付利息!");
    }

    /**
     * 状态转换
     */
    @Override
    public void stateCheck() {
    
    
        if (acc.getBalance() > -2000 && acc.getBalance() <= 0) {
    
    
            acc.setState(new OverdraftState(this));
        }
        else if (acc.getBalance() == -2000) {
    
    
            acc.setState(new RestrictedState(this));
        }
        else if (acc.getBalance() < -2000) {
    
    
            System.out.println("操作受限!");
        }
    }
}

//透支状态:具体状态类

class OverdraftState extends AccountState
{
    
    
    public OverdraftState(AccountState state) {
    
    
        this.acc = state.acc;
    }

    @Override
    public void deposit(double amount) {
    
    
        acc.setBalance(acc.getBalance() + amount);
        stateCheck();
    }

    @Override
    public void withdraw(double amount) {
    
    
        acc.setBalance(acc.getBalance() - amount);
        stateCheck();
    }

    @Override
    public void computeInterest() {
    
    
        System.out.println("计算利息!");
    }

    /**
     * 状态转换
     */
    @Override
    public void stateCheck() {
    
    
        if (acc.getBalance() > 0) {
    
    
            acc.setState(new NormalState(this));
        }
        else if (acc.getBalance() == -2000) {
    
    
            acc.setState(new RestrictedState(this));
        }
        else if (acc.getBalance() < -2000) {
    
    
            System.out.println("操作受限!");
        }
    }
}

//受限状态:具体状态类

class RestrictedState extends AccountState {
    
    
    public RestrictedState(AccountState state) {
    
    
        this.acc = state.acc;
    }

    @Override
    public void deposit(double amount) {
    
    
        acc.setBalance(acc.getBalance() + amount);
        stateCheck();
    }

    @Override
    public void withdraw(double amount) {
    
    
        System.out.println("帐号受限,取款失败");
    }

    @Override
    public void computeInterest() {
    
    
        System.out.println("计算利息!");
    }

    /**
     * 状态转换
     */
    @Override
    public void stateCheck() {
    
    
        if(acc.getBalance() > 0) {
    
    
            acc.setState(new NormalState(this));
        }
        else if(acc.getBalance() > -2000) {
    
    
            acc.setState(new OverdraftState(this));
        }
    }
}

4.5.2 Client

package com.zyz.demo;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/5/28 21:53
 * @Description:
 */
public class Client {
    
    
    public static void main(String args[]) {
    
    
        Account acc = new Account("段誉",0.0);
        acc.deposit(1000);
        acc.withdraw(2000);
        acc.deposit(3000);
        acc.withdraw(4000);
        acc.withdraw(1000);
        acc.computeInterest();
    }
}

4.5.3 Test results

insert image description here

4.6. Pattern Analysis

  • The state pattern describes the changes in the state of an object and how the object behaves differently in each state.
  • The key to the state pattern is to introduce an abstract class to specifically represent the state of the object. This class is called an abstract state class, and each specific state class of the object inherits this class, and implements different types in different specific state classes. State behavior, including transitions between various states.
    In the state pattern structure, it is necessary to understand the role of the environment class and the abstract state class:
  • The environment class is actually an object with a state. Sometimes the environment class can act as a state manager (State Manager), and the state can be switched in the environment class.
  • The abstract state class can be an abstract class or an interface. Different state classes are different subclasses that inherit this parent class. The state class is generated because there are multiple states in the environment class, and it also meets two conditions: These states often need Toggle, the object behaves differently in different states. Therefore, the behavior under different objects can be extracted and encapsulated in a specific state class, so that the environment class object can change its behavior when its internal state changes. The object seems to modify its class, but in fact it is due to Switch to a different concrete state class implementation. Since the environment class can be set to any specific state class, it is programmed for the abstract state class, and the object of any specific state class can be set to the environment class when the program is running, so that the environment class can change the internal state, and change behavior.

4.7. Examples

The example of TCPConnection
      comes from "Design Patterns", which shows a simplified version of the TCP protocol implementation; there are many possible states of the TCP connection, and there are corresponding logical prerequisites for the transition between states; this is the occasion for using the state pattern; the structure diagram
:
insert image description here

Timing diagram
insert image description here

4.8. Advantages

Advantages of the state pattern

  • Encapsulates transformation rules.
  • To enumerate the possible states, it is necessary to determine the state type before enumerating the states.
  • Put all the behaviors related to a certain state into a class, and you can easily add new states, and you only need to change the state of the object to change the behavior of the object.
  • Allows state transition logic to be integrated into the state object, rather than a giant block of conditional statements.
  • Multiple environment objects can share a state object, thereby reducing the number of objects in the system.

4.9. Disadvantages

Disadvantages of the state pattern

  • The use of state mode will inevitably increase the number of system classes and objects.
  • The structure and implementation of the state pattern are relatively complicated, and if used improperly, it will lead to confusion in the program structure and code.
  • The state model does not support the "opening and closing principle" very well. For a state model that can switch states, adding a new state class requires modifying the source code responsible for state transitions, otherwise it cannot be switched to the new state; and modifying a The behavior of the state class also needs to modify the source code of the corresponding class.

4.10. Applicable environment

The state pattern can be used when:

  • The behavior of an object depends on its state (properties) and can change its related behavior according to its state change.
  • The code contains a large number of conditional statements related to the state of the object. The appearance of these conditional statements will lead to poor maintainability and flexibility of the code, and the state cannot be easily added and deleted, which will strengthen the coupling between the client class and the class library. . The behavior of the object is included in these conditional statements, and these conditions correspond to various states of the object.

4.11. Schema Application

      The state mode is widely used in software such as workflow or games, and can even be used in the core function design of these systems. For example, in the government OA office system, there are multiple states of an approval document: not yet processed; in process; in progress Approval; reviewing; completed and other states, and the operation of the approval is different when the approval status is different. Use the state pattern to describe the state transition of a workflow object (such as a batch) and its behavior in different states.

4.12. Schema Extensions

shared state

  • In some cases, multiple environment objects need to share the same state. If you want multiple environment object instances to share one or more state objects in the system, you need to define these state objects as static member objects of the environment.

Simple State Pattern vs. State Pattern with Toggleable States

  1. Simple state mode: The simple state mode refers to the state mode in which the states are independent of each other and there is no need to switch between states. This is the simplest state mode. For this state mode, each state class encapsulates state-related operations without concern about state switching. You can directly instantiate the state class on the client, and then set the state object to the environment class. If it is this simple state mode, it follows the "open-close principle". On the client side, you can program the abstract state class and write the specific state class into the configuration file. At the same time, adding a new state class will also affect the original system. No effect.
  2. State mode with switchable state: Most of the state modes are state modes that can switch states. When implementing state switching, it is necessary to call the setState() method of the environment class Context to perform state conversion operations in the specific state class. The method of the environment class can be called in the state class, so there is usually an association or dependency relationship between the state class and the environment class. State switching is achieved by calling back the setState() method of the environment class by referring to the object of the environment class in the state class. In this state mode that can switch states, adding a new state class may require modifying the source code of some other state classes or even environment classes, otherwise the system cannot switch to the newly added state.

4.13. Summary

  • The state pattern allows an object to change its behavior when its internal state changes, and the object appears to modify its class. Its alias is the state object, and the state pattern is an object behavior pattern.
  • The state pattern contains three roles: the environment class is also called the context class, which is an object with a state, and maintains an instance of an abstract state class State in the environment class. This instance defines the current state. In the specific implementation, it is a State The object of the subclass can define the initial state; the abstract state class is used to define an interface to encapsulate the behavior related to a specific state of the environment class; the concrete state class is a subclass of the abstract state class, and each subclass implements a state related to the environment A state-related behavior of a class, each specific state class corresponds to a specific state of the environment, and different specific state classes have different behaviors.
  • The state pattern describes the changes in the state of an object and how the object behaves differently in each state.
  • The main advantage of the state pattern is that it encapsulates the transition rules and enumerates the possible states. It puts all the behaviors related to a certain state into a class, and can easily add new states, only need to change the state of the object Changing the behavior of objects can also allow multiple environment objects to share a state object, thereby reducing the number of objects in the system; the disadvantage is that the use of state patterns will increase the number of system classes and objects, and the structure and implementation of state patterns are different. It is more complicated. If it is used improperly, it will lead to confusion of program structure and code. The state mode that can switch states does not meet the requirements of the "opening and closing principle".
  • The application of the state pattern includes: the behavior of an object depends on its state (properties) and its related behavior can be changed according to its state change; the code contains a large number of conditional statements related to the state of the object, and the appearance of these conditional statements will As a result, the maintainability and flexibility of the code deteriorate, the state cannot be added and deleted conveniently, and the coupling between the client class and the class library is enhanced.

4.14. Handle multiple states of objects and their mutual conversion

Use the environment class to realize the state transition
      When implementing the state transition in the state mode, the specific state class can perform the state transition operation by calling the setState() method of the environment class Context, or the state transition can be realized uniformly by the environment class Context . At this time, adding a new specific state class may require modifying the source code of other specific state classes or environment classes, otherwise the system cannot switch to the newly added state. But for the client, there is no need to care about the state class, and the default state class can be set for the environment class, and the state conversion work is handed over to the specific state class or environment class to complete. The specific conversion details are transparent to the client of.

      In the above "bank account state transition" example, we realize the state transition through specific state classes, and each specific state class contains a stateCheck() method, and the state transition is realized inside this method. In addition, we can also implement state transitions through the environment class. The environment class acts as a state manager to uniformly implement the transition operations between various states .

      Let's illustrate how to use the environment class to implement state transitions through a simple example that includes a loop state:

      A developer of Sunny Software Company intends to develop a screen magnifier tool. Its specific functions are described as follows:
After the user clicks the "Magnifying Glass" button, the screen will be doubled, and the screen will be doubled after clicking the "Magnifying Glass" button again. The screen will return to the default size after pressing this button.
      You can consider using the state mode to design the screen magnifier tool. We define three screen state classes NormalState, LargerState and LargestState to correspond to the three states of the screen, which are normal state, double magnification state and quadruple magnification state, and the screen class Screen Acting as an environment class, its structure is shown in Figure 6:
insert image description here

The core code of this example is as follows:

package com.zyz.demo1;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/5/28 22:12
 * @Description:
 */

//屏幕类

class Screen {
    
    
    //枚举所有的状态,currentState表示当前状态
    private State currentState, normalState, largerState, largestState;

    public Screen() {
    
    
        this.normalState = new NormalState(); //创建正常状态对象
        this.largerState = new LargerState(); //创建二倍放大状态对象
        this.largestState = new LargestState(); //创建四倍放大状态对象
        this.currentState = normalState; //设置初始状态
        this.currentState.display();
    }

    public void setState(State state) {
    
    
        this.currentState = state;
    }

    /**
     * 单击事件处理方法,封转了对状态类中业务方法的调用和状态的转换
     */
    public void onClick() {
    
    
        if (this.currentState == normalState) {
    
    
            this.setState(largerState);
            this.currentState.display();
        }
        else if (this.currentState == largerState) {
    
    
            this.setState(largestState);
            this.currentState.display();
        }
        else if (this.currentState == largestState) {
    
    
            this.setState(normalState);
            this.currentState.display();
        }
    }
}


//抽象状态类

abstract class State {
    
    
    public abstract void display();
}

//正常状态类

class NormalState extends State{
    
    
    @Override
    public void display() {
    
    
        System.out.println("正常大小!");
    }
}


//二倍状态类

class LargerState extends State{
    
    
    @Override
    public void display() {
    
    
        System.out.println("二倍大小!");
    }
}


//四倍状态类

class LargestState extends State{
    
    
    @Override
    public void display() {
    
    
        System.out.println("四倍大小!");
    }


}

      In the above code, all state transition operations are implemented by the environment class Screen, at this time, the environment class acts as a state manager. If you need to add a new state, such as "eightfold state class", you need to modify the environment class, which violates the "opening and closing principle" to a certain extent, but has no effect on other state classes.
      Write the following client code for testing:

package com.zyz.demo1;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/5/28 22:13
 * @Description: 客户端
 */
class Client {
    
    
    public static void main(String args[]) {
    
    
        Screen screen = new Screen();
        screen.onClick();
        screen.onClick();
        screen.onClick();
    }
}

The output is as follows:
insert image description here

Guess you like

Origin blog.csdn.net/weixin_43304253/article/details/130986343
Recommended