JS设计模式-状态模式

151 阅读2分钟
定义

允许一个对象在其内部状态改变时改变他的行为,对象看起来似乎修改了它的类。

例子:图片上传

1)常规处理 image.png

<template>
<div>
  <plugin ref="plugin"></plugin>
  <span>文件名称1:{{fileName}}</span>
  <button data-action="button1" @click="button1Click">{{button1Text}}</button>
  <button data-action="button2" @click="button2Click">删除中</button>
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import plugin from '@/components/plugin.vue'

export default defineComponent({
  components: {
    plugin
  },
  data() {
    return {
      fileName: '',
      state: 'sign',
      button1Text: '扫描中',
      delFlag: false,
    }
  },
  created()  {
    this.fileName = '文件名';
    window.externalUpload = (state: string) => this.changeState(state);
    setTimeout(() => {
      window.externalUpload('uploading')
    }, 1000);
    setTimeout(() => {
      window.externalUpload('done')
    }, 5000);
  },
  methods: {
    button1Click() {
      if (this.state === 'sign') {
        console.log('扫描中,点击无效')
      } else if (this.state === 'uploading') {
        this.changeState('pause')
      } else if (this.state === 'pause') {
        this.changeState('uploading')
      } else if (this.state === 'done') {
        console.log('文件上传成功,点击无效')
      } else if (this.state === 'error') {
        console.log('文件上传失败,点击无效')
      }
    },
    button2Click() {
      if (this.state === 'done' || this.state === 'error' || this.state === 'pause') {
        this.changeState('del')
      } else if (this.state === 'sign') {
        console.log('文件正在扫描中,不能删除')
      } else if (this.state === 'pause') {
        console.log('文件正在上传中,不能删除')
      }
    },
    changeState(state: string) {
      // console.log(this.$refs.plugin.done)
      switch (state) {
        case 'sign':
          this.$refs.plugin.sign();
          this.button1Text = '扫描中,任何操作无效';
          break;
        case 'uploading':
          this.$refs.plugin.uploading();
          this.button1Text = '正在上传,点击暂停';
          break;
        case 'pause':
          this.$refs.plugin.pause();
          this.button1Text = '已暂停,点击继续上传';
          break;
        case 'done':
          this.$refs.plugin.done();
          this.button1Text = '上传完成';
          break;
        case 'error':
          this.button1Text = '上传失败';
          break;
        case 'del':
          this.$refs.plugin.del();
          console.log('删除完成')
          break;
      }
      this.state = state;
    }
  }
})
</script>

2)状态模式封装 image.png

<template>
<div>
  <plugin ref="plugin"></plugin>
  <span>文件名称:{{fileName}}</span>
  <button data-action="button1" @click="button1Click">{{button1Text}}</button>
  <button data-action="button2" @click="button2Click">删除中</button>
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import plugin from '@/components/plugin.vue'
import { UploadingState, SignState, PauseState, DoneState, ErrorState } from '@/utils/stateFac'

export default defineComponent({
  components: {
    plugin
  },
  data() {
    return {
      fileName: '',
      state: 'sign',
      currState: '',
      button1Text: '',
      uploadingState: new UploadingState(this),
      signState: new SignState(this),
      pauseState: new PauseState(this),
      doneState: new DoneState(this),
      errorState: new ErrorState(this),
    }
  },
  created()  {
    this.fileName = '文件名';
    window.externalUpload = (state: string) => this['changeState'](state);
  },
  mounted() {
    this.$nextTick(() => {
      window.externalUpload('sign')
    })
    setTimeout(() => {
      window.externalUpload('uploading')
    }, 1000);
    setTimeout(() => {
      window.externalUpload('done')
    }, 5000);
  },
  methods: {
    button1Click() {
      this.currState.clickHandler1();
    },
    button2Click() {
      this.currState.clickHandler2();
    },
    changeState(state: string) {
      this.$refs.plugin[state]();
      this.currState = this[`${state}State`];
      const obj = {
        sign: '扫描中,任何操作无效',
        uploading: '正在上传,点击暂停',
        pause: '已暂停,点击继续上传',
        done: '上传完成',
        error: '上传失败',
      }
      if (obj[state]) this.button1Text = obj[state];
    },
  }
})
</script>
// stateFac.js
export class State {
  clickHandler1(){console.log('clickHandler1')}
  clickHandler2(){console.log('clickHandler2')}
}
export class StateFactory extends State {
  constructor(param) {
    super(param)
    this.uploadObj = param;
  }
}
export class SignState extends StateFactory {
  constructor(param) {
    super(param)
  }
  clickHandler1(){console.log('扫描中,点击无效')}
  clickHandler2(){console.log('文件正在上传中,不能删除')}
}
export class UploadingState extends StateFactory {
  constructor(param) {
    super(param)
  }
  clickHandler1(){this.uploadObj.changeState('pause')}
  clickHandler2(){console.log('文件正在上传中,不能删除')}
}
export class PauseState extends StateFactory {
  constructor(param) {
    super(param)
  }
  clickHandler1(){this.uploadObj.changeState('uploading')}
  clickHandler2(){this.uploadObj.changeState('del')}
}
export class DoneState extends StateFactory {
  constructor(param) {
    super(param)
  }
  clickHandler1(){console.log('文件已完成上传,点击无效')}
  clickHandler2(){this.uploadObj.changeState('del')}
}
export class ErrorState extends StateFactory {
  clickHandler1(){console.log('文件上传失败,点击无效')}
  clickHandler2(){this.uploadObj.changeState('del')}
}
优点
  • 状态模式定义了状态与行为之间的关系,并将他们封装在一个类里,通过新增的状态类,很容易增加新的状态和转换。
  • 避免context无限膨胀,状态切换的逻辑被分布在状态中,也去掉了context中原本过多的条件分支。
  • 用对象代替字符串来记录当前状态,使状态的切换一目了然。
  • context的请求动作和状态类中封装的行为可以容易的独立变化而互不影响。
缺点
  • 编写状态类会让系统增加不少对象。
  • 逻辑分散在状态类中,无法在一个地方就看出现整个状态机的逻辑。
应用场景

操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态,那么可以使用状态模式来将分支的处理分散到单独的状态类中;
对象的行为随着状态的改变而改变,那么可以考虑状态模式,来把状态和行为分离,虽然分离了,但是状态和行为是对应的,再通过改变状态调用状态对应的行为;

  • 下拉菜单在hover动作下有显示、悬浮、隐藏等状态。
  • 一次TCP请求有建立连接、监听、关闭等状态。
  • 游戏任务有攻击、防御、跳跃、跌倒等状态。