javascript 設計模式之狀態模式

文章系列

javascript 設計模式之單例模式javascript

javascript 設計模式之適配器模式java

javascript 設計模式之裝飾者模式jquery

javascript設計模式之代理模式web

javascript 適配、代理、裝飾者模式的比較算法

javascript 設計模式之狀態模式npm

javascript 設計模式之迭代器模式設計模式

javascript 設計模式之策略模式promise

javascript 設計模式之觀察者模式markdown

javascript 設計模式之發佈訂閱者模式app

概念

狀態模式:容許一個對象在其內部狀態改變時改變它的行爲,對象看起來彷佛修改了它的類。

從電燈提及

需求:

實現一個開關功能的電燈

代碼實現:

class Light {
    constructor() {
        this.state = 'off' // 默認關燈
        this.button = null
    }
    init() {
        const button = document.createElement('button')
        this.button = document.body.appendChild(button)
        this.button.innerHTML = '開關'
        this.button.onclick = () => {
            this.handleStateChange()
        }
    }

    handleStateChange() {
        if(this.state == 'off') {
            console.info("開燈")
            this.state = 'on'
        } else if(this.state == 'on') {
            console.info('關燈')
            this.state = 'off'
        }
    }
}

const light = new Light()
light.init()
複製代碼

這個功能暫時告一段落。

但過不久產品經理要求實現這樣一種電燈:第一次按下打開弱光,第二次按下打開強光,第三次纔是關閉電燈。

你只得更改 handleStateChange 代碼爲

handleStateChange () {
    if (this.state === 'off') {
      console.log('弱光')
      this.state = 'weakLight'
    } else if (this.state === 'weakLight') {
      console.log('強光')
      this.state = 'strongLight'
    } else if (this.state === 'strongLight') {
      console.log('關燈')
      this.state = 'off'
    }
}
複製代碼

功能是實現了,但存在以下問題:

  • 違反單一功能原則,Light 類中應該只負責狀態的切換,不該該包含各類狀態的邏輯處理
  • 違反開放封閉原則,若是要再加個魔幻燈關類型,還得往 handleStateChange 函數裏添加一段邏輯,這樣提測的時候,還要求測試人員重頭將全部電燈狀態測遍。
  • 經過 if else 來作狀態切換,若是要實現同時知足多個狀態啥的,就會很難維護

優化一(採用狀態模式)

封裝通常是封裝對象的行爲,而不是封裝對象的狀態,可是在狀態模式中,關鍵的就是把每種狀態封裝成單獨的類,跟狀態相關的行爲都封裝在類的內部

首先先拆解出該電燈存在三種狀態:OffLightState (關燈)、WeakLightState (弱光)、StrongLightState (強光)。 編寫狀態類以下:

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

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

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

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

    handleStateChange () {
        console.log('關燈')
        this.light.setState(this.light.offLightState)
    }
}
複製代碼

編寫 Light 類: 採用狀態對象的形式標識當前的開關狀態,而不是以前的字符串( '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 // 初始化電燈狀態
      this.button = null
    }
  
    init () {
      const button = document.createElement('button')
      this.button = document.body.appendChild(button)
      this.button.innerHTML = '開關'
  
      this.button.onclick = () => {
        this.currentState.handleStateChange()
      }
    }
  
    setState (newState) {
      this.currentState = newState
    }
  }
  
  const light = new Light()
  light.init()
複製代碼

有以下優勢:

  • 每種狀態和它對應的行爲之間的關係局部化,這些行爲被分散在各個對象的狀態類之中,便於閱讀和管理。
  • 狀態之間的切換邏輯分佈在狀態類內部,這使得咱們無需編寫if-else語句來控制狀態直接的切換。
  • 當咱們須要爲 Light 類增長一種新的狀態時,只須要增長一個新的狀態類,再稍微改變一下現有的代碼。

優化二(JavaScript版本狀態模式)

非面向對象實現的策略方式,將各個狀態定義在一個對象裏,經過對象映射的方式,以及 call 語法綁定主體類 Light

const lightState = {
  'offLight': {
    handleStateChange:function() {
      console.log('弱光')
      this.setState(lightState.weakLight)
    }
  },
  'weakLight': {
    handleStateChange:function() {
      console.log('強光')
      this.setState(lightState.strongLight)
    }
  },
  'strongLight': {
    handleStateChange:function() {
      console.log('關燈')
      this.setState(lightState.offLight)
    }
  }
}

class Light {
  constructor () {
    this.currentState = lightState.offLight // 初始化電燈狀態
    this.button = null
  }

  init () {
    console.info(this,"this")
    const button = document.createElement('button')
    this.button = document.body.appendChild(button)
    this.button.innerHTML = '開關'
    this.button.onclick = () => {
      this.currentState.handleStateChange.call(this) // 經過 call 完成委託
    }
  }

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

複製代碼

狀態模式使用場景

  • 一個對象的行爲取決於它的狀態,而且它必須在運行時刻根據狀態改變它的行爲。
  • 一個操做中含有大量的分支語句,並且這些分支語句依賴於該對象的狀態。狀態一般爲一個或多個枚舉常量的表示。

採用 javascript-state-machine 完成狀態模式

好比實現個收藏,與取消收藏功能

import StateMachine from 'javascript-state-machine'
import $ from 'jquery'
var fsm = new StateMachine({
	init: '收藏',
	transitions: [
		{ name: 'doStore', from: '收藏', to: '取消收藏' },
		{ name: 'deleteStore', from: '取消收藏', to: '收藏' }
	],
	methods: {
		onDoStore: function () {
			console.log('收藏成功')
			updateText()
		},
		onDeleteStore: function () {
			console.log('取消收藏成功')
			updateText()
		}
	}
})
const updateText = function () {
	$('#btn1').text(fsm.state)
}

$('#btn1').on('click', function () {
	if (fsm.is('收藏')) {
		fsm.doStore()
	} else {
		fsm.deleteStore()
	}
})
// 初始化
updateText()

複製代碼

採用狀態模式實現 promise

promise 的 pending fulfilled rejected 是三個不一樣的狀態,而且每種狀態都有相應的行爲

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')
})

複製代碼

只是實現 promise 功能的一部分,但已足夠說明狀態模式的使用方式

狀態模式與策略模式的比較

相同點:

它們都有一個上下文、一些策略類或者狀態類,上下文把請求委託給這些類來執行

不一樣點:

策略模式:各個策略類之間是平等又平行的,它們之間沒有任何關係,因此客戶必須熟知這些策略類的做用,以便客戶本身能夠隨時主動切換算法。

狀態模式:狀態和狀態對應的行爲早已被封裝好,狀態之間的切換也早就被規定,「改變行爲」這件事發生在狀態模式的內部,對於客戶來講,不須要了解這些細節。好比電燈的開與關,是由程序切換的,不用用戶傳入狀態值。

參考連接

JavaScript設計模式與開發實踐

結語

你的點贊是對我最大的確定,若是以爲有幫助,請留下你的讚揚,謝謝!!!