用英雄联盟的方式讲解JavaScript设计模式(二)

前言

你们好,这是第三篇做者对于设计模式的分享了,前两篇能够参考:javascript

  • 手写一下JavaScript的几种设计模式 (工厂模式,单例模式,适配器模式,装饰者模式,建造者模式)前端

  • 用英雄联盟的方式讲解JavaScript设计模式(一)! (构造函数模式,外观模式,代理模式)java

设计模式在编程开发中用途十分普遍,每个模式描述了一个在咱们周围不断重复发生的问题,以及解决问题的核心!不少的时候,对于咱们其实如何选择适合的设计模式,才更加消耗时间。从以前的文章,每个设计模式都会有一到两个例子,既能够给本身之后开发回忆设计模式提供帮助,也但愿能够给读者一些启发。web

策略模式



简介

策略模式定义了算法家族,分别封装起来,让他们之间能够互相替换,此模式让算法的变化不会影响到使用算法的客户。ajax

那听起来云山雾绕,怎么都涉及到 算法 了 ?难道我一个前端是时候进攻算法大军了吗。其实并非,用一个超级常见的例子就能够解释!算法

让咱们又回到英雄联盟,当咱们第一次登录英雄联盟的时候,须要输入一个新的姓名吧?起名规则起码得有如下这几条:typescript

  • 名字长度编程

  • 名字是否有非法字符设计模式

  • 是否重名数组

  • 不能为空

其中具体的设定,只有开发者才知道了,身为玩家只能注意到这几点,那策略模式怎么体如今这里的呢?首先咱们实现一个显而易见功能的例子:

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

虽然能够获得理想的结果,但这种写法有十分严重的缺点,最重要的,每次增长或修改规则时,须要修改整个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) // truecheck('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) > 5if (switchVD) { var result = new Victory()} else { var result = new Defeat()}
result.show() // loser or winner

上述代码能够轻松的看到,若是没有桥接模式,直接把实现层,渲染层糅合在一块儿,会依赖上下文。假若获取不到上下文的环境,很容易出现问题。

小结

桥接模式在平常开发中,会在不经意间频繁使用,目的也是为了让代码结构清晰,将不一样逻辑的代码互相解耦。便于往后维护,开发时也更能区分模块,看的舒服,天然效率也高。

桥接模式关键是要理解抽象部分与实现部分的分离,使得两者能够独立的变化,而没必要拘泥于形式。灵活的变化,适用场景的多变就很是适合使用这种模式来实现。桥接模式最重要的是找到代码中不一样的变化纬度。

状态模式

简介

状态模式(State)容许一个对象在其内部状态改变的时候改变它的行为,对象看起来彷佛修改了它的类。其实就是用一个对象或者数组记录一组状态,每一个状态对应一个实现,实现的时候根据状态挨个去运行实现。

优势:

  1. 一个状态对应一个行为,直观清晰,增改方便。

  2. 状态与状态间,行为与行为间彼此独立互不干扰。

  3. 避免对象条件判断语句过多。

  4. 不用执行没必要要的判断语句。

缺点:

  1. 须要将事物的不一样状态以及对应的行为拆分出来,有时候会过分设计。

  2. 必然会增长事物类和动做类的个数,动做类再根据单一原则,分别拆成几个类,会反而使得代码混乱。

好比下面咱们定义一个英雄的状态,名字叫亚索,其中亚索可能同时有好几个状态好比 边走边攻击 —— 咱们俗称的“走A”,还有可能释放技能以后接一个“B键回家”的操做,固然最有可能的是eqw闪r行云流水的操做收获一我的头,再接一个ctrl+f6等。

若是对这些操做一个个进行处理判断,须要多个if-elseswitch不只丑陋不说,并且在遇到有组合动做的时候,实现就会更为冗余。那么咱们这里的复杂操做,可使用 状态模式 来实现。

状态模式 的思路是:首先建立一个状态对象或者数组,在对象内部存储须要操做的状态数组或对象,而后状态对象提供一些接口,能够更改状态以及执行动做。

那如今有一个英雄叫作亚索!下面代码,咱们就用亚索的状态来实现一下传说中的状态模式:

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 行,让人跪服,就突出一个词 —— 优雅。

相关参考

  • tom大叔博客

  • 《设计可复用的设计模式》

源自:https://juejin.im/post/5ec791fdf265da770d3daabd

声明:文章著做权归做者全部,若有侵权,请联系小编删除。

感谢 · 转发欢迎你们留言

本文分享自微信公众号 - web前端学习圈(web-xxq)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索