前端JS高频面试题---7.状态模式

711 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第18天,点击查看活动详情

什么是“状态模式”?

状态模式:对象行为是根据状态改变,而改变的。

特点:正是由于内部状态的变化,导致对外的行为发生了变化。例如:相同的方法在不同时刻被调用,行为可能会有差异。

何时使用:代码中包含大量与对象状态有关的条件语句。

如何解决:将各种具体的状态类抽象出来。

优缺点

优点:

  • 封装了转化规则,对于大量分支语句,可以考虑使用状态类进一步封装。
  • 每个状态都是确定的,对象行为是可控的。

缺点:

  • 状态模式的实现关键是将事物的状态都封装成单独的类,状态模式的使用必然会增加系统类和对象的个数。 这个类的各种方法就是“此种状态对应的表现行为”。因此,程序开销会增大。

使用场景

1、行为随状态改变而改变的场景。

2、条件、分支语句的代替者。

状态模式实现手电筒关灯、强光、弱光三种状态的切换

const obj = {
    weakLight: {
        press() {
            console.log('打开强光');
            this.currState = obj.strongLight;
        }
    },
    strongLight: {
        press() {
            console.log('关灯');
            this.currState = obj.offLight;
        }
    },
    offLight: {
        press() {
            console.log('打开弱光');
            this.currState = obj.weakLight;
        }
    }
}

const Light = function() {
    this.currState = obj.offLight;
}

const btn = (() => {
    const btn = document.createElement('button');
    btn.innerHTML = 'Press Me';
    document.body.appendChild(btn);
    return btn;
})()

Light.prototype.handlePress = function() {
    let self = this;
    btn.addEventListener('click', function() {
        self.currState.press.call(self);
    }, false)
}

const light = new Light();
light.handlePress();

obj为一个状态对象,保存了三种状态模式的操作及具体实现,当触发对应的操作时,立即将当前状态切换为下一个,实现有限状态机这样一种模式,handlePress就是常说的状态类,它的行为随着状态的改变而改变。 下面是ES6的 简单实现

const obj = {
    weakLight: {
        press() {
            console.log('打开强光');
            this.currState = obj.strongLight;
        }
    },
    strongLight: {
        press() {
            console.log('关灯');
            this.currState = obj.offLight;
        }
    },
    offLight: {
        press() {
            console.log('打开弱光');
            this.currState = obj.weakLight;
        }
    }
}

const btn = (() => {
    const btn = document.createElement('button');
    btn.innerHTML = 'Press Me';
    document.body.appendChild(btn);
    return btn;
})()

class Light {
    constructor(obj) {
        this.currState = obj.offLight;
    }

    handlePress() {
        this.currState.press.call(this);
    }
}

const light = new Light(obj);
btn.addEventListener('click', () => {
    light.handlePress();
}, false)

也可将状态对象放在类得内部:

class Test {
  constructor(state) {
    this.state = state;
  }

  actionLight = {
    that: this,
    off() {
      console.log('打开弱光');
      this.that.state = 'weak'

    },

    weak() {
      console.log('打开强光');
      this.that.state = 'strong'

    },

    strong() {
      console.log('关灯');
      this.that.state = 'off'

    }
  }
  changeState() {
    this.actionLight[this.state]();
  }
}
let a = new Test('off');
a.changeState(); // 打开弱光
a.changeState(); // 打开强光
a.changeState(); // 关灯
a.changeState(); // 打开弱光
a.changeState(); // 打开强光
a.changeState(); // 关灯

总结

状态模式的优点:

  • 结构相比之下清晰,避免了过多的 switch-case 或 if-else 语句的使用,避免了程序的复杂性提高系统的可维护性;
  • 符合开闭原则,每个状态都是一个子类,增加状态只需增加新的状态类即可,修改状态也只需修改对应状态类就可以了;
  • 封装性良好,状态的切换在类的内部实现,外部的调用无需知道类内部如何实现状态和行为的变换。

状态模式的缺点:

  • 引入了多余的类,每个状态都有对应的类,导致系统中类的个数增加。

状态模式的适用场景:

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

状态模式与其他设计模式的区别

状态模式和策略模式

状态模式和策略模式在之前的代码就可以看出来,看起来比较类似,他们的区别:

  • 状态模式: 重在强调对象内部状态的变化改变对象的行为,状态类之间是平行的,无法相互替换;
  • 策略模式: 策略的选择由外部条件决定,策略可以动态的切换,策略之间是平等的,可以相互替换; 状态模式的状态类是平行的,意思是各个状态类封装的状态和对应的行为是相互独立、没有关联的,封装的业务逻辑可能差别很大毫无关联,相互之间不可替换。但是策略模式中的策略是平等的,是同一行为的不同描述或者实现,在同一个行为发生的时候,可以根据外部条件挑选任意一个实现来进行处理。

状态模式和发布-订阅模式

这两个模式都是在状态发生改变的时候触发行为,不过发布-订阅模式的行为是固定的,那就是通知所有的订阅者,而状态模式是根据状态来选择不同的处理逻辑。

  • 状态模式: 根据状态来分离行为,当状态发生改变的时候,动态地改变行为;
  • 发布-订阅模式: 发布者在消息发生时通知订阅者,具体如何处理则不在乎,或者直接丢给用户自己处理; 这两个模式是可以组合使用的,比如在发布-订阅模式的发布消息部分,当对象的状态发生了改变,触发通知了所有的订阅者后,可以引入状态模式,根据通知过来的状态选择相应的处理。

状态模式和单例模式

之前的示例代码中,状态类每次使用都 new 出来一个状态实例,实际上使用同一个实例即可,因此可以引入单例模式,不同的状态类可以返回的同一个实例。

感谢

谢谢你读完本篇文章,希望对你能有所帮助,如有问题欢迎各位指正。

我是Nicnic,如果觉得写得可以的话,请点个赞吧❤。

写作不易,「点赞」+「在看」+「转发」 谢谢支持❤

往期好文

《# Electron--快速入门》

《# Javascript高频手写面试题》

《# 高频CSS面试题》

《# JavaScript设计模式-前端开发不迷路》

《# vue组件汇总》