This is the first day of my participation in the August More Text challenge
The state pattern is used relatively infrequently, mainly because it introduces a large number of state classes, making the code difficult to maintain. But using state patterns in the right scenarios simplifies complex judgment logic.
UML class diagrams location: www.processon.com/view/link/6…
This article is linked to: github.com/shidawuhen/…
Definition 1.
1.1 Status Mode
State mode: Allows an object to change its behavior when its internal state changes, as if the object has changed its class.
UML:
1.2 analysis
A State machine consists of three parts: states, events, and Actions. Events trigger transitions of state and execution of actions.
Looking at definitions and UML alone, it may be difficult to understand the benefits of using state patterns, as an example makes clear.
Suppose there are four states A, B, C, and D, with four trigger events E1, E2, E3, and E4. Without the state mode, it would look like this:
func E1(a) {
if status == "A" {
// State migration + action execution
} else if status == "B" {
// State migration + action execution
} else if status == "C" {
// State migration + action execution
} else if status == "D" {
// State migration + action execution}}func E2(a) {
if status == "A" {
// State migration + action execution
} else if status == "B" {
// State migration + action execution
} else if status == "C" {
// State migration + action execution
} else if status == "D" {
// State migration + action execution}}Copy the code
The pseudo-code may look fine on its own, but if you think about it more closely, the code might look ugly if the actions are more complex to perform. If the state or event changes later, how do you ensure that everything is changed? This is where the state mode comes into play.
We create four classes, such as ConcreteStateA, ConcreteStateB, ConcreteStateC, ConcreteStateD in UML, which represent four states respectively. Each state class has four Handles that correspond to four events. In this way, the tangled logic is broken down and the code looks much more elegant.
2. Application scenarios
Having not used state mode for so many years, the usage scenarios are a bit sparse.
In actual business scenarios, the state machine that has done cross-border performance order needs to go through such states as receiving order, customs clearance, successful customs clearance and shipment. This scenario is relatively simple, in which the state can only flow in one direction, and a single interface triggers the flow of the specified state to the next state. The tricky part is that the next state may have multiple states, some of which can be skipped. In this case, using arrays to maintain the state machine is better than using state mode.
If there are a lot of states and the execution logic of the actions is complex, then it makes sense to use state modes, which are often used more in games. Using super Mario as an example in Design Mode Beauty, the introduction of Super Mario is very appropriate, one is because Mario has multiple states and trigger events, which are particularly suitable for using state mode. Second, everyone has played Super Mario, and everyone is familiar with the business situation. In order to help you recall, I find the Mario series of Alt mode all zhuanlan.zhihu.com/p/250931383…
3. Code implementation
Mario states include Small Mario, Super Mario, and Cape Mario. Little Mario eats mushrooms to become Super Mario, and little Mario and Super Mario get capes to become Cape Mario. Super Mario and The Cape Mario meets a monster and turns into Little Mario.
package main
import "fmt"
type Mario struct {
score int64
status MarioStatus
}
type MarioStatus interface {
Name()
ObtainMushroom()
ObtainCape()
MeetMonster()
SetMario(mario *Mario)
}
/** * @author: Jason Pang * @description: Mario */
type SmallMarioStatus struct {
mario *Mario
}
/** * @author: Jason Pang * @description: Set Mario * @receiver s * @param Mario */
func (s *SmallMarioStatus) SetMario(mario *Mario) {
s.mario = mario
}
func (s *SmallMarioStatus) Name(a) {
fmt.Println("Little Mario")}/** * @author: Jason Pang * @description: Get mushroom as Super Mario * @Receiver s */
func (s *SmallMarioStatus) ObtainMushroom(a) {
s.mario.status = &SuperMarioStatus{
mario: s.mario,
}
s.mario.score += 100
}
/** * @author: Jason Pang * @description: Get the cloak changed into The Cloak Mario * @receiver s */
func (s *SmallMarioStatus) ObtainCape(a) {
s.mario.status = &CapeMarioStatus{
mario: s.mario,
}
s.mario.score += 200
}
/** * @author: Jason Pang * @description: Meet monsters minus 100 * @receiver s */
func (s *SmallMarioStatus) MeetMonster(a) {
s.mario.score -= 100
}
/** * @author: Jason Pang * @description: Super Mario */
type SuperMarioStatus struct {
mario *Mario
}
/** * @author: Jason Pang * @description: Set Mario * @receiver s * @param Mario */
func (s *SuperMarioStatus) SetMario(mario *Mario) {
s.mario = mario
}
func (s *SuperMarioStatus) Name(a) {
fmt.Println("Super Mario")}/** * @author: Jason Pang * @description: Get mushrooms with no change * @receiver s */
func (s *SuperMarioStatus) ObtainMushroom(a){}/** * @author: Jason Pang * @description: Get the cloak changed into The Cloak Mario * @receiver s */
func (s *SuperMarioStatus) ObtainCape(a) {
s.mario.status = &CapeMarioStatus{
mario: s.mario,
}
s.mario.score += 200
}
/** * @author: Jason Pang * @description: Meet the monster to become little Mario * @receiver s */
func (s *SuperMarioStatus) MeetMonster(a) {
s.mario.status = &SmallMarioStatus{
mario: s.mario,
}
s.mario.score -= 200
}
/** * @author: Jason Pang * @description: Mario */
type CapeMarioStatus struct {
mario *Mario
}
/** * @author: Jason Pang * @description: Set Mario * @receiver s * @param Mario */
func (c *CapeMarioStatus) SetMario(mario *Mario) {
c.mario = mario
}
func (c *CapeMarioStatus) Name(a) {
fmt.Println(Mario the Cape)}/** * @author: Jason Pang * @description: Get no change in mushroom * @receiver c */
func (c *CapeMarioStatus) ObtainMushroom(a){}/** * @author: Jason Pang * @description: Get cloak without change * @receiver c */
func (c *CapeMarioStatus) ObtainCape(a){}/** * @author: Jason Pang * @description: Meet the monster to become little Mario * @receiver c */
func (c *CapeMarioStatus) MeetMonster(a) {
c.mario.status = &SmallMarioStatus{
mario: c.mario,
}
c.mario.score -= 200
}
func main(a) {
mario := Mario{
status: &SmallMarioStatus{},
score: 0,
}
mario.status.SetMario(&mario)
mario.status.Name()
fmt.Println("------------------- get mushroom \n")
mario.status.ObtainMushroom()
mario.status.Name()
fmt.Println("------------------- get cloak \n")
mario.status.ObtainCape()
mario.status.Name()
fmt.Println("------------------- Meet monster \n")
mario.status.MeetMonster()
mario.status.Name()
}
Copy the code
Output:
➜ myproject go run main.go
Small Mario
——————- get mushrooms
Super Mario
——————- Get cape
Mario the Cape
——————- Meet the monster
Small Mario
conclusion
Take a closer look at the code above
-
Changes to the transition of the event-triggered state and the execution of the action can be simple
-
New events can be added quickly
-
It is also easy to add new states by adding new state classes and making minor changes to existing code
The downside is that if there are too many classes, there will be too many functions in the class, even if those functions are useless. But it’s worth it to get better scalability.
The last
If you like my article, you can follow my public account (Programmer Malatang)
My personal blog is shidawuhen.github. IO /
Review of previous articles:
-
Design patterns
-
recruitment
-
thinking
-
storage
-
The algorithm series
-
Reading notes
-
Small tools
-
architecture
-
network
-
The Go