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设计模式与开发实践

结语

你的点赞是对我最大的确定,若是以为有帮助,请留下你的赞扬,谢谢!!!