设计模式之-状态模式

状态模式是一种非同寻常的优秀模式,他也许是解决某些需求场景的最好方法
状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。下面来看一个电灯程序,代码如下:

javascript 复制代码
class Light{
    constructor(){
        this.state='off'; // 给电灯设置初始状态off
        this.button=null; // 电灯开关按钮
    }
    init(){
        const button=document.createElement('button');
        button.innerHTML='开关';
        this.button=document.body.appendChild(button);
        this.button.onclick=()=>{
            this.buttonWasPressed();
        }
    }
    buttonWasPressed(){
        if(this.state==='off'){
            console.log('开灯');
            this.state='on';
        }else if(this.state==='on'){
            console.log('关灯');
            this.state='off';
        }
    }
}
const light=new Light();
light.init();

上面是一个反例,现在我们使用状态模式改进电灯的程序。通常我们谈到封装,一般都会优先封装对象的行为,而不是对象的状态。但在状态模式正好相反,状态模式是把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部,所以button按钮被按下的时候,只需要在上下文中,把这个请求委托给当前的状态对象即可,该状态对象会负责渲染他自身的行为。
状态模式改进电灯程序,添加弱光,强光两种状态

javascript 复制代码
// OffLightState
class OffLightState{
    constructor(light){
        this.light=light;
    }
    buttonWasPressed(){
        console.log('弱光');
        this.light.setState(this.light.weakLightState);
    }
}
// WeakLightState
class WeakLightState{
    constructor(light){
        this.light=light;
    }
    buttonWasPressed(){
        console.log('强光');
        this.light.setState(this.light.strongLightState);
    }
}
// StrongLightState
class StrongLightState{
    constructor(light){
        this.light=light;
    }
    buttonWasPressed(){
        console.log('关灯');
        this.light.setState(this.light.offLightState);
    }
}

class Light{
    constructor(){
        this.offLightState = new OffLightState(this);
        this.weakLightState = new WeakLightState(this);
        this.strongLightState = new StrongLightState(this);
        this.button = null;
    }
    init(){
        const button=document.createElement('button');
        button.innerHTML='开关';
        this.button=document.body.appendChild(button);
        this.currState = this.offLightState; //设置当前状态
        this.button.onclick=()=>{
            this.currState.buttonWasPressed();
        }
    }
    setState(newState){
        this.currState=newState;
    }
}
const light=new Light();
light.init();

如果后面需要加超强光,超超强光状态,就只需要添加新的状态类就可以了
通过电灯的例子,相信我们对于状态模式已经有了一定程度的了解。现在回头看GoF中对状态模式的定义:允许一个对象在其内部状态改变时改变他的行为,对象看起来似乎修改了他的类
这句话第一部分的意思是将状态封装成独立的类,并将请求委托给当前对象,当对象的内部状态改变时,会带来不同的行为变化。电灯的例子足以说明这一点,在off和on这两种不同状态需啊,我们点击同一个按钮,得到的行为反馈是截然不同的。
第二部分是从客户的角度来啦吗,我们使用的对象,在不同的状态下具有截然不同的行为,这个对象看起来是从不同的类中实例化而来的,实际上这是使用了委托的效果。
文件上传的例子

javascript 复制代码
window.external.upload = function (state) {
    console.log(state); // 可能为sign,uploading,done,error
};

var plugin = (function () {
    var plugin = document.createElement('embed');
    plugin.style.display = 'none';
    plugin.type = 'application/txftn-webkit';
    plugin.sign = function () {
        console.log('开始文件扫描');
    };
    plugin.pause = function () {
        console.log('暂停文件上传');
    };
    plugin.uploading = function () {
        console.log('开始文件上传');
    };
    plugin.del = function () {
        console.log('删除文件上传');
    };
    plugin.done = function () {
        console.log('文件上传完成');
    };
    document.body.appendChild(plugin);
    return plugin;
})();

var Upload = function (fileName) {
    this.plugin = plugin;
    this.fileName = fileName;
    this.button1 = null;
    this.button2 = null;
    this.state = 'sign'; // 设置初始状态为waiting
}

Upload.prototype.init = function () {
    this.dom = document.createElement('div');
    this.dom.innerHTML = `<span>文件名称: ${this.fileName}</span>
        <button data-action='button1'>扫描中</button>
         <button data-action='button2'>删除</button>
    `;
    document.body.appendChild(this.dom);
    this.button1 = this.dom.querySelector('[data-action="button1"]');
    this.button2 = this.dom.querySelector('[data-action="button2"]');
    this.bindEvent();
};

Upload.prototype.bindEvent = function () {
    var self = this;
    this.button1.onclick = function () {
        if (self.state === 'sign') { // 扫描状态下,任何操作无效
            console.log('扫描中,点击无效。。。');
        } else if (self.state === 'uploading') { // 上传中,点击切换到暂停
            self.changeState('pause');
        } else if (self.state === 'pause') { // 暂停中,点击切换到上传中
            self.changeState('uploading');
        } else if (self.state === 'done') { // 上传中,点击切换到暂停
            console.log('文件已完成上传,点击无效。。。');
        } else if (self.state === 'error') { // 上传中,点击切换到暂停
            console.log('文件上传失败,点击无效。。。');
        }
    };
    this.button2.onclick = function () {
        if (self.state === 'done' || self.state == 'error' || self.state == 'pause') {
            // 上传失败,上传完成和暂停状态下可以删除
            self.changeState('del');
        } else if (self.state === 'sign') {
            console.log('文件正在扫描中,不能删除。。。');
        } else if (self.state === 'uploading') {
            console.log('文件正在上传中,不能删除。。。');
        }
    }
};

Upload.prototype.changeState = function (state) {
    switch (state) {
        case 'sign':
            this.plugin.sign();
            this.button1.innerHTML = '扫描中,任何操作无效';
            break;
        case 'uploading':
            this.plugin.uploading();
            this.button1.innerHTML = '正在上传,点击暂停';
            break;
        case 'pause':
            this.plugin.pause();
            this.button1.innerHTML = '已暂停,点击继续上传';
            break;
        case 'done':
            this.plugin.done();
            this.button1.innerHTML = '上传完成';
            break;
        case 'error':
            this.button1.innerHTML = '上传失败';
            break;
        case 'del':
            this.plugin.del();
            this.dom.parentNode.removeChild(this.dom);
            this.button1.innerHTML = '删除完成';
            break;
    }
    this.state=state;
};

var uploadObj = new Upload('Javascript设计模式与开发实践');
uploadObj.init();
window.external.upload=function(state){ // 插件调用javascript的方法
    uploadObj.changeState(state);
};
window.external.upload('sign'); // 文件开始扫描

setTimeout(function(){
    window.external.upload('uploading'); // 2秒后开始上传
},2000);

setTimeout(function(){
    window.external.upload('done'); // 9秒后上传完成
},9000);

状态模式重构文件上传代码

javascript 复制代码
window.external.upload = function (state) {
    console.log(state); // 可能为sign,uploading,done,error
};

var plugin = (function () {
    var plugin = document.createElement('embed');
    plugin.style.display = 'none';
    plugin.type = 'application/txftn-webkit';
    plugin.sign = function () {
        console.log('开始文件扫描');
    };
    plugin.pause = function () {
        console.log('暂停文件上传');
    };
    plugin.uploading = function () {
        console.log('开始文件上传');
    };
    plugin.del = function () {
        console.log('删除文件上传');
    };
    plugin.done = function () {
        console.log('文件上传完成');
    };
    document.body.appendChild(plugin);
    return plugin;
})();

var Upload = function (fileName) {
    this.plugin = plugin;
    this.fileName = fileName;
    this.button1 = null;
    this.button2 = null;

    this.signState = new SignState(this); // 设置初始状态为waiting
    this.uploadingState = new UploadingState(this);
    this.pauseState = new PauseState(this);
    this.doneState = new DoneState(this);
    this.errorState = new ErrorState(this);
    this.currState = this.signState; // 设置当前状态
}

Upload.prototype.init = function () {
    this.dom = document.createElement('div');
    this.dom.innerHTML = `<span>文件名称: ${this.fileName}</span>
        <button data-action='button1'>扫描中</button>
         <button data-action='button2'>删除</button>
    `;
    document.body.appendChild(this.dom);
    this.button1 = this.dom.querySelector('[data-action="button1"]');
    this.button2 = this.dom.querySelector('[data-action="button2"]');
    this.bindEvent();
};

Upload.prototype.bindEvent = function () {
    var self = this;
    this.button1.onclick=function(){
        self.currState.clickHandler1();
    }
    this.button2.onclick=function(){
        self.currState.clickHandler2();
    }
};

Upload.prototype.sign=function(){
    this.plugin.sign();
    this.currState=this.signState;
}
Upload.prototype.uploading=function(){
    this.button1.innerHTML = '正在上传中,点击暂停';
    this.plugin.uploading();
    this.currState=this.uploadingState;
}
Upload.prototype.pause=function(){
    this.button1.innerHTML='已暂停,点击继续上传';
    this.plugin.pause();
    this.currState=this.pauseState;
}

Upload.prototype.done=function(){
    this.button1.innerHTML='上传完成';
    this.plugin.done();
    this.currState=this.doneState;
}
Upload.prototype.error=function(){
    this.button1.innerHTML='上传失败';
    this.currState=this.errorState;
}
Upload.prototype.del=function(){
    this.plugin.done();
    this.dom.parentNode.removeChild(this.dom);
}

var StateFactory = (function(){
    var State = function(){};
    State.prototype.clickHandler1 = function(){
        throw new Error('子类必须重写父类的clickHandler1方法');
    }
    State.prototype.clickHandler2 = function(){
        throw new Error('子类必须重写父类的clickHandler2方法');
    }
    return function(param){
        var F=function(uploadObj){
            this.uploadObj=uploadObj;
        }
        F.prototype=new State();
        for(var i in param){
            F.prototype[i]=param[i];
        }
        return F;
    }
})();

var SignState = StateFactory({
    clickHandler1:function(){
        console.log('扫描中,点击无效。。。');
    },
    clickHandler2:function(){
        console.log('文件正在上传中,不能删除。。。');
    },
});

var UploadingState = StateFactory({
    clickHandler1:function(){
        this.uploadObj.pause();
    },
    clickHandler2:function(){
        console.log('文件正在上传中,不能删除。。。');
    },
});
var PauseState = StateFactory({
    clickHandler1:function(){
        this.uploadObj.uploading();
    },
    clickHandler2:function(){
        this.uploadObj.del();
    },
});
var DoneState = StateFactory({
    clickHandler1:function(){
         console.log('文件已完成上传,点击无效。。。');
    },
    clickHandler2:function(){
        this.uploadObj.del();
    },
});
var ErrorState = StateFactory({
    clickHandler1:function(){
         console.log('文件上传失败,点击无效。。。');
    },
    clickHandler2:function(){
        this.uploadObj.del();
    },
});

var uploadObj = new Upload('Javascript设计模式与开发实践');
uploadObj.init();
window.external.upload=function(state){ // 插件调用javascript的方法
    uploadObj[state]();
};
window.external.upload('sign'); // 文件开始扫描

setTimeout(function(){
    window.external.upload('uploading'); // 2秒后开始上传
},2000);

setTimeout(function(){
    window.external.upload('done'); // 9秒后上传完成
},9000);

非原创,来源javascript设计模式与开发实践 -曾探

相关推荐
未寒2 小时前
关于uni app vue2 和vue3 的区别
前端·javascript·vue.js·uni-app
Aevget2 小时前
DevExtreme JS & ASP.NET Core v25.2新功能预览 - 字体栈、可访问性升级增强
javascript·asp.net·界面控件·devexpress·ui开发·devextreme
IT古董2 小时前
企业级官网全栈(React·Next.js·Tailwind·Axios·Headless UI·RHF·i18n)实战教程-第四篇:登录与注册系统(核心篇)
javascript·react.js·ui
山沐与山2 小时前
【设计模式】Python仓储模式:从入门到实战
python·设计模式
q150803962252 小时前
数据整理无忧:深度评测高效文本合并工具的实用功能
开发语言·前端·javascript
华仔啊2 小时前
async/await 到底要不要加 try-catch?异步错误处理最佳实践
前端·javascript
开发者小天2 小时前
React中useCallback的使用
前端·javascript·react.js·typescript·前端框架·css3·html5
开发者小天2 小时前
React中的useState传入函数的好处
前端·javascript·react.js
Violet_YSWY2 小时前
Vue import.meta.env 讲解
前端·javascript·vue.js