你们好,这是第三篇做者对于设计模式的分享了,前两篇能够参考:javascript
手写一下JavaScript的几种设计模式 (工厂模式,单例模式,适配器模式,装饰者模式,建造者模式)html
用英雄联盟的方式讲解JavaScript设计模式(一)! (构造函数模式,外观模式,代理模式)前端
设计模式在编程开发中用途十分普遍,每个模式描述了一个在咱们周围不断重复发生的问题,以及解决问题的核心!不少的时候,对于咱们其实如何选择适合的设计模式,才更加消耗时间。从以前的文章,每个设计模式都会有一到两个例子,既能够给本身之后开发回忆设计模式提供帮助,也但愿能够给读者一些启发。java
策略模式定义了算法家族,分别封装起来,让他们之间能够互相替换,此模式让算法的变化不会影响到使用算法的客户。ajax
那听起来云山雾绕,怎么都涉及到 算法 了 ?难道我一个前端是时候进攻算法大军了吗。其实并非,用一个超级常见的例子就能够解释!算法
让咱们又回到英雄联盟,当咱们第一次登录英雄联盟的时候,须要输入一个新的姓名吧?起名规则起码得有如下这几条:编程
其中具体的设定,只有开发者才知道了,身为玩家只能注意到这几点,那策略模式怎么体如今这里的呢?首先咱们实现一个显而易见功能的例子:设计模式
var validator = {
validate: function (value, type) {
switch (type) {
case 'isNonEmpty ':
{
return true; // 名字不能为空
}
case 'isNoNumber ':
{
return true; // 名字 不是 纯数字
break;
}
case 'isExist ':
{
return true; // 名字已存在
}
case 'isLength':
{
return true; // 长度合理
}
}
}
};
复制代码
上述代码能够实现一个表单验证系统,刚建立角色起名字的时候验证那里的功能,只须要传入相应的参数就能够。数组
validator.validate('测试名字', 'isNumber') // false
bash
虽然能够获得理想的结果,但这种写法有十分严重的缺点,最重要的,每次增长或修改规则时,须要修改整个validate
函数,这不符合开放封闭原则,增长逻辑,让函数更加复杂不可控。
那真正适合的代码应该怎么写呢?
var validator = {
// 全部验证规则处理函数存放的地方
types: {},
validate: function (str, types) {
this.messages = [];
var checker, result, msg, i;
for (i in types) {
var type = types[i];
checker = this.types[type]; // 获取验证规则的验证类
if (!checker) { // 若是验证规则类不存在,抛出异常
throw {
name: "ValidationError",
message: "No handler to validate type " + type
};
}
result = checker.validate(str); // 使用查到到的单个验证类进行验证
if (!result) {
msg = "Invalid value for *" + type + "*, " + checker.instructions;
this.messages.push(msg);
}
}
return this.hasErrors();
},
// 是否有message错误信息
hasErrors: function () {
return this.messages.length !== 0;
}
};
复制代码
上面的代码定义了validator
对象以及validate
函数,函数内部会对传入的字符串,检测类型数组进行处理。若是存在规则,进行判断,并把错误信息发送到this.message
。若是不存在规则,天然的就不须要继续执行,抛出error
便可。
// 验证给定的值是否不为空
validator.types.isNonEmpty = {
validate: function (value) {
return value !== "";
},
instructions: "传入的值不能为空"
};
// 验证给定的值是否 不是 纯数字
validator.types.isNoNumber = {
validate: function (value) {
return isNaN(value); // 伪写法,由于isNaN会误判布尔值和空字符串等,所以并不能做为真正判断纯数字的依据
},
instructions: "传入的值不能是纯数字"
};
// 验证给定的值是否存在
validator.types.isExist = {
validate: function (value) {
// $.ajax() ...
return true;
},
instructions: "给定的值已经存在"
};
// 验证给定的值长度是否合理
validator.types.isLength = {
validate: function (value) {
var l = value.toString().length
if ( l > 2 && l < 10) {
return true;
} else {
return false;
}
},
instructions: "长度不合理,请长度在2-10个字符内"
};
复制代码
上面对types
规则进行了补充,定义了几种规则,至此,对于名称校验,简单的设定就敲完了。接下来要准备的就是一个可以在英雄联盟合理的名字进行验证:
var types = ['isExist', 'isLength', 'isNoNumber', 'isNonEmpty']; // 决定想要的规则,不管增长或者减小,原函数都不须要改动
function check (name, types) {
validator.validate(name, types);
if (validator.hasErrors()) {
console.log(validator.messages.join("\n"));
} else {
console.log('验证经过!')
}
}
check('okckokckokck', types) // 长度不合理,请长度在2-10个字符内
check('老faker', types) // true
check('00001', types) // 传入的值不能是纯数字
复制代码
首先设定好想要的规则,用一个types
数组囊括进来,以后定义一个check
函数,把结果处理封装一下,最后传入参数,不管想要检测什么规则,都不须要修改原函数。如今不管我想检测faker
可不能够注册,仍是一个空字符串,均可以传入规则,进行使用。若是想添加新的规则,只须要在validator.types
上续写对象就能够,方便清晰,结构明朗。
核心思想就是把复杂的算法结构,分别封装起来,让他们之间能够互相替换,上面的代码就很好的体现了 互相替换 ,由于不管我怎么去修改想要的规则,都不须要改动本来的代码。
在系统沿着多个维度变化的同时,又不增长其复杂度并已达到解耦。将抽象部分与它的实现部分分离,使它们均可以独立地变化。简单的说:桥接模式最主要的特色是实现层(如元素绑定的事件)与抽象层(如修饰页面UI逻辑)解耦分离。
下面依然是一个例子:
假如咱们还在英雄联盟的世界里,每一场游戏最终都会有一个结局,不管胜利仍是失败,都会弹出一个窗口,告诉你 —— Victory或者是Defeat。
function GameMessage (type) { // 抽象 与 实现 的 桥梁
this.fn = type ? new Victory() : new Defeat()
}
GameMessage.prototype.show = function() {
this.fn.show()
}
function Defeat() { // 抽象层
this.show = function() {
console.log('im loser')
}
}
function Victory() { // 抽象层
this.show = function() {
console.log('im winner')
}
}
// 实现层
function getResult() {
var switchVD = Math.ceil(Math.random()*10) > 5 // 胜利失败一半一半
return new GameMessage(switchVD)
}
var result1 = getResult()
var result2 = getResult()
var result3 = getResult()
result1.show()
result2.show()
result3.show()
复制代码
首先咱们建立了一个GameMessage
的函数,咱们都知道胜利失败都有一半的几率,所以定义了switchVD
变量,模拟一个随机事件,同时每次结果调用一次getResult
函数,获取最新结果。
桥接模式体如今GameMessage
函数上,将抽象的 Victory()
以及 Defeat()
与 咱们获取结果的 getResult()
实现解耦。函数之间不糅合逻辑,但又经过桥梁函数,链接在一块儿。
这么写的好处就是,二者均可以独立的变化,互不打扰。毕竟若是揉在一块儿,可能逻辑以下:
function Defeat() { // 抽象层
this.show = function() {
console.log('im loser')
}
}
function Victory() { // 抽象层
this.show = function() {
console.log('im winner')
}
}
var switchVD = Math.ceil(Math.random()*10) > 5
if (switchVD) {
var result = new Victory()
} else {
var result = new Defeat()
}
result.show() // loser or winner
复制代码
上述代码能够轻松的看到,若是没有桥接模式,直接把实现层,渲染层糅合在一块儿,会依赖上下文。假若获取不到上下文的环境,很容易出现问题。
桥接模式在平常开发中,会在不经意间频繁使用,目的也是为了让代码结构清晰,将不一样逻辑的代码互相解耦。便于往后维护,开发时也更能区分模块,看的舒服,天然效率也高。
桥接模式关键是要理解抽象部分与实现部分的分离,使得两者能够独立的变化,而没必要拘泥于形式。灵活的变化,适用场景的多变就很是适合使用这种模式来实现。桥接模式最重要的是找到代码中不一样的变化纬度。
状态模式(State)容许一个对象在其内部状态改变的时候改变它的行为,对象看起来彷佛修改了它的类。 其实就是用一个对象或者数组记录一组状态,每一个状态对应一个实现,实现的时候根据状态挨个去运行实现。
优势:
缺点:
好比下面咱们定义一个英雄的状态,名字叫亚索,其中亚索可能同时有好几个状态好比 边走边攻击 —— 咱们俗称的“走A”,还有可能释放技能以后接一个“B键回家”的操做,固然最有可能的是eqw闪r行云流水的操做收获一我的头,再接一个ctrl+f6
等。
若是对这些操做一个个进行处理判断,须要多个if-else
或switch
不只丑陋不说,并且在遇到有组合动做的时候,实现就会更为冗余。那么咱们这里的复杂操做,可使用 状态模式 来实现。
状态模式 的思路是:首先建立一个状态对象或者数组,在对象内部存储须要操做的状态数组或对象,而后状态对象提供一些接口,能够更改状态以及执行动做。
那如今有一个英雄叫作亚索!下面代码,咱们就用亚索的状态来实现一下传说中的状态模式:
function YasuoState() {
//存储当前即将执行动做的状态!
this.currentstate = [];
this.Actions = {
walk : function(){
console.log('walk');
},
attack : function(){
console.log('attack');
},
magic : function(){
console.log('magic');
},
backhome : function(){
console.log('backhome');
}
};
}
YasuoState.prototype.changeState = function() {
//清空当前的动做
this.currentstate = [];
Object.keys(arguments).forEach((i) => this.currentstate.push(arguments[i]))
return this;
}
YasuoState.prototype.YasuoActions = function() {
//当前动做集合中的动做依次执行
this.currentstate.forEach((k) => this.Actions[k] && this.Actions[k]())
return this;
}
var yasuoState = new YasuoState();
yasuoState.changeState('walk','attack').YasuoActions().changeState('walk').YasuoActions().YasuoActions();
复制代码
上面代码成功实现了亚索的状态模式,咱们假设他有走路、攻击、释放技能、回家几个状态,其中这几个状态实际上是能够同时输入指令的,要否则那些职业选手的高光操做就会在 技能衔接 而出现的卡顿 香消玉殒。
状态模式最多见的就是平常的例子 —— 红绿灯,每当切换状态的时候,执行一次动做。
至于英雄联盟中,最多见的就是边走边攻击,在输入命令后,首先改变了咱们对象的状态yasuoState.changeState('magic','backhome')
,而后由于在代码中有return this;
,能够链式调用接下来的行为,因而咱们让它依次执行刚才输入的状态。接下来又一次改变了状态changeState('walk')
,而且进行执行。能够看到执行了两次,因为状态并无再次改变,所以只须要重复执行就能够保证咱们的英雄一直往前走下去了。
但愿状态模式能够帮助你解决绝大多数,须要切换状态的操做。遇到相似的问题时,能够迅速拿出成熟可靠的状态模式解决之。
本次分享的三种模式,均可以在英雄联盟中找到影子,由于我喜欢这款游戏,因此很轻松能够找到其中使用的设计模式:
设计模式主要能够帮助咱们解决,开发中对代码的设计问题,那咱们如何找到合适的对象,并应用合适的设计模式呢?
借用书中的几个提示吧:
寻找合适的对象
决定对象的粒度
决定好这个对象设计的接口
把对象须要的具体函数实现
合理的运用代码复用机制
设计的代码应该能够支持变化,要对变化有预见性
大概是这几种,在 javascript 中涉及编译的场景较少,就不叙述了。
设计模式是软件开发人员在软件开发过程当中面临的通常问题的解决方案。这些解决方案是众多软件开发人员通过至关长的一段时间的试验和错误总结出来的。
根据上面的几条规则,在开发接口和函数的时候,时刻注意,就能够避免大多数代码设计上的问题,对于之后的维护也会有巨大的帮助。下一个接受代码的人,也会十分感激你的,读代码其实和读书同样,你如今偷懒写的代码可能无所谓,后面接手的人会疯狂吐槽。相反若是你优雅的实现,像我,就会内心由衷的佩服,看到整齐的函数,注释明朗的功能,不得不说,高手确实是高手啊,短短 200 行,让人跪服,就突出一个词 —— 优雅。