State mode of javascript Design Pattern

concept

State mode: allows an object to change its behavior when its internal state changes. The object seems to modify its class.

Start with the electric light

Requirements:

An electric lamp that realizes a switching function

Code implementation:

class Light {
    constructor() {
        this.state = 'off' // Default light off
        this.button = null
    }
    init() {
        const button = document.createElement('button')
        this.button = document.body.appendChild(button)
        this.button.innerHTML = 'switch'
        this.button.onclick = () => {
            this.handleStateChange()
        }
    }

    handleStateChange() {
        if(this.state == 'off') {
            console.info("turn on the light")
            this.state = 'on'
        } else if(this.state == 'on') {
            console.info('Turn off the lights')
            this.state = 'off'
        }
    }
}

const light = new Light()
light.init()

This function has come to an end for the time being.

But before long, the product manager asked to realize such an electric lamp: press it for the first time to turn on the weak light, press it for the second time to turn on the strong light, and turn off the electric lamp for the third time.
You have to change the handleStateChange code to

handleStateChange () {
    if (this.state === 'off') {
      console.log('Weak light')
      this.state = 'weakLight'
    } else if (this.state === 'weakLight') {
      console.log('Strong light')
      this.state = 'strongLight'
    } else if (this.state === 'strongLight') {
      console.log('Turn off the lights')
      this.state = 'off'
    }
}

The function is realized, but there are the following problems:

  • In violation of the principle of single function, the Light class should only be responsible for the switching of States and should not include the logical processing of various states
  • In violation of the open and closed principle, if you want to add a magic light off type, you have to add a piece of logic to the handleStateChange function. In this way, the tester is also required to test all performance levels again.
  • If you want to switch states through if else, it will be difficult to maintain if you want to meet multiple states at the same time

Optimization I (using state mode)

Encapsulation is generally to encapsulate the behavior of the object, not the state of the object. However, in the state mode, the key is to encapsulate each state into a separate class, and the behavior related to the state is encapsulated inside the class
First of all, disassemble the lamp to find that there are three states: off light state, weak light state and strong light state.
The writing status classes are as follows:

class OffLightState {
    construct (light) {
      this.light = light
    }
  
    handleStateChange () {
      console.log('Weak light')
      this.light.setState(this.light.weakLightState)
    }
}

class WeakLightState {
    construct (light) {
        this.light = light
    }

    handleStateChange () {
        console.log('Strong light')
        this.light.setState(this.light.strongLightState)
    }
}

class StrongLightState {
    construct (light) {
        this.light = light
    }

    handleStateChange () {
        console.log('Turn off the lights')
        this.light.setState(this.light.offLightState)
    }
}

Write a Light class:
Use the situation of the status object to identify the current switch status, instead of the previous string ('weakLight ',' strongLight ',' off ')

class Light {
    construct () {
      this.offLightState = new OffLightState(this)
      this.weakLightState = new WeakLightState(this)
      this.strongLightState = new StrongLightState(this)
  
      this.currentState = this.offLightState // Initialize light state
      this.button = null
    }
  
    init () {
      const button = document.createElement('button')
      this.button = document.body.appendChild(button)
      this.button.innerHTML = 'switch'
  
      this.button.onclick = () => {
        this.currentState.handleStateChange()
      }
    }
  
    setState (newState) {
      this.currentState = newState
    }
  }
  
  const light = new Light()
  light.init()

It has the following advantages:

  • The relationship between each state and its corresponding behavior is localized. These behaviors are scattered in the state classes of each object for easy reading and management.
  • The switching logic between States is distributed within the state class, which makes it unnecessary to write if else statements to control the direct switching of states.
  • When we need to add a new state for the Light class, we only need to add a new state class and change the existing code slightly.

Optimization 2 (JavaScript version policy mode)

The strategy of non object-oriented implementation defines each state in an object, and binds the main class Light through object mapping and call syntax

const lightState = {
  'offLight': {
    handleStateChange:function() {
      console.log('Weak light')
      this.setState(lightState.weakLight)
    }
  },
  'weakLight': {
    handleStateChange:function() {
      console.log('Strong light')
      this.setState(lightState.strongLight)
    }
  },
  'strongLight': {
    handleStateChange:function() {
      console.log('Turn off the lights')
      this.setState(lightState.offLight)
    }
  }
}

class Light {
  constructor () {
    this.currentState = lightState.offLight // Initialize light state
    this.button = null
  }

  init () {
    console.info(this,"this")
    const button = document.createElement('button')
    this.button = document.body.appendChild(button)
    this.button.innerHTML = 'switch'
    this.button.onclick = () => {
      this.currentState.handleStateChange.call(this) // Complete the delegation through call
    }
  }

  setState (newState) {
    this.currentState = newState
  }
}
  
const light = new Light()
light.init()

State mode usage scenario

  • The behavior of an object depends on its state, and it must change its behavior according to the state at runtime.
  • An operation contains a large number of branch statements, and these branch statements depend on the state of the object. A state is usually a representation of one or more enumeration constants.

use javascript-state-machine Completion status configuration

For example, implement a collection and cancel the collection function

import StateMachine from 'javascript-state-machine'
import $ from 'jquery'
var fsm = new StateMachine({
	init: 'Collection',
	transitions: [
		{ name: 'doStore', from: 'Collection', to: 'Cancel collection' },
		{ name: 'deleteStore', from: 'Cancel collection', to: 'Collection' }
	],
	methods: {
		onDoStore: function () {
			console.log('Collection success')
			updateText()
		},
		onDeleteStore: function () {
			console.log('Cancel collection successfully')
			updateText()
		}
	}
})
const updateText = function () {
	$('#btn1').text(fsm.state)
}

$('#btn1').on('click', function () {
	if (fsm.is('Collection')) {
		fsm.doStore()
	} else {
		fsm.deleteStore()
	}
})
// initialization
updateText()

Implement promise with state mode

The pending fully rejected of promise is three different states, and each state has corresponding behavior

import StateMachine from 'javascript-state-machine'
var fsm = new StateMachine({
	init: 'pending',
	transitions: [
		{ name: 'resolve', from: 'pending', to: 'fulfilled' },
		{ name: 'reject', from: 'pending', to: 'rejected' }
	],
	methods: {
		onResolve: function (state, data) {
			data.successCallBacks.forEach((fn) => fn())
		},
		onReject: function () {
			data.failCallBacks.forEach((fn) => fn())
		}
	}
})
class MyPromise {
	constructor(fn) {
		this.successCallBacks = []
		this.failCallBacks = []
		fn(
			() => {
				fsm.resolve(this)
			},
			() => {
				fsm.reject(this)
			}
		)
	}
	then(successCall, failCall) {
		this.successCallBacks.push(successCall)
		this.failCallBacks.push(failCall)
	}
}

const loadImg = function (src) {
	const promise = new MyPromise((resolve, reject) => {
		const img = document.createElement('img')
		img.onload = function () {
			resolve(img)
		}
		img.onerror = function (err) {
			reject(err)
		}
		img.src = src
	})
	return promise
}
var src = ' //www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png'
let result = loadImg(src)
result.then(function (img) {
	console.info('ok1')
})
result.then(function (img) {
	console.info('ok2')
})

It is only a part of realizing promise function, but it is enough to explain the use of state mode

Comparison between state mode and policy mode

Similarities:
They all have a context, some policy or state classes, and the context delegates the request to these classes to execute
difference:
Policy mode: all policy classes are equal and parallel, and there is no relationship between them. Therefore, customers must be familiar with the role of these policy classes, so that customers can actively switch algorithms at any time.
State mode: the state and the behavior corresponding to the state have long been encapsulated, and the switching between States has long been stipulated. "Changing behavior" occurs inside the state mode. For customers, they don't need to know these details. For example, the on and off of electric lights are switched by the program without the user entering the state value.

Reference link

JavaScript Design Pattern and development practice

epilogue

Your praise is my greatest affirmation. If it is helpful, please leave your appreciation, thank you!!!

Keywords: Javascript Design Pattern

Added by nowaydown1 on Thu, 10 Feb 2022 03:28:50 +0200