热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

重构设计API的扩展机制

1.前言上篇文章,主要介绍了重构的一些概念和一些简单的实例。这一次,详细的说下项目中的一个重构场景--给API设计扩展机制。目的就是为了方便以后能灵活应

1.前言

上篇文章,主要介绍了重构的一些概念和一些简单的实例。这一次,详细的说下项目中的一个重构场景--给API设计扩展机制。目的就是为了方便以后能灵活应对需求的改变。当然了,是否需要设计扩展性这个要看API的需求。如果大家有什么建议,欢迎评论留言。

2.扩展性表现形式

2-1.prototype

这个可以说是JS里面最原的一个扩展。比如原生JS没有提供打乱数组顺序的API,但是开发者又想方便使用,这样的话,就只能扩展数组的prototype。代码如下

//扩展Array.prototype,增加打乱数组的方法。
Array.prototype.upset=function(){return this.sort((n1,n2)=>Math.random() - 0.5);
}let arr=[1,2,3,4,5];
//调用
arr.upset();
//显示结果
console.log(arr);

运行结果

clipboard.png

功能是实现了。但是上面的代码,只想借用例子讲解扩展性,大家看下就好。不要模仿,也不要在项目这样写。现在基本都禁止这样开发了。理由也很简单,之前的文章也有提到过。这里重复一下。

这样就污染了原生对象Array,别人创建的Array也会被污染,造成不必要的开销。最可怕的是,万一自己命名的跟原生的方法重名了,就被覆盖原来的方法了。

Array.prototype.push=function(){console.log('守候')}
let arrTest=[123]
arrTest.push()
//result:守候
//push方法有什么作用,大家应该知道,不知道的可以去w3c看下

clipboard.png

2-2.jQuery

关于 jQuery 的扩展性,分别提供了三个API:$.extend()、$.fn和$.fn.extend()。分别对jQuery的本身,静态方法,原型对象进行扩展,基于jQuery写插件的时候,最离不开的应该就是$.fn.extend()。

参考链接:

理解jquery的$.extend()、$.fn和$.fn.extend()
Jquery自定义插件之$.extend()、$.fn和$.fn.extend()

2-3.VUE

对VUE进行扩展,引用官网(插件)的说法,扩展的方式一般有以下几种:

1.添加全局方法或者属性,如: vue-custom-element

2.添加全局资源:指令/过滤器/过渡等,如 vue-touch

3.通过全局 mixin 方法添加一些组件选项,如: vue-router

4.添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。

5.一个库,提供自己的 API,同时提供上面提到的一个或多个功能,如 vue-router

基于VUE的扩展。在组件,插件的内容提供一个install方法。如下

clipboard.png

使用组件

clipboard.png

上面几个扩展性的实例分别是原生对象,库,框架的扩展,大家可能觉得有点夸夸而谈,那下面就分享一个日常开发常用的一个实例。

3.实例-表单验证

看了上面那些扩展性的实例,下面看下一个在日常开发使用得也很多的一个实例:表单验证。这块可以说很简单,但是做好,做通用不简单。看了《Javascript设计模式与开发实践》,用策略模式对以前的表单验证函数进行了一个重构。下面进行一个简单的分析。

下面的内容,代码会偏多,虽然代码不难,但还是强烈建议大家不要只看,要边看,边写,边调试,不然作为读者,很可能不知道我的代码是什么意思,很容易懵。下面的代码回涉两个知识:开放-封闭原则和策略模式,大家可以自行了解。

3-1.原来方案

/*** @description 字段检验* @param checkArr* @returns {boolean}*/
function validateForm(checkArr){let _reg = null, ruleMsg, nullMsg, lenMsg;for (let i = 0, len = checkArr.length; i checkArr[i].maxLength) {return lenMsg;}}}return false;
}

函数调用方式

let testData={phone:'18819323632',pwd:'112'}let _tips = validateForm([{el: testData.phone, noNull: true, nullMsg: '电话号码不能为空',rule: "mobile", msg: '电话号码格式错误'},{el: testData.pwd, noNull: true, nullMsg: '密码不能为空',lenMsg:'密码长度不正确',minLength:6,maxLength:18}]);//字段验证如果返回错误信息if (_tips) {alert(_tips);}

3-2.存在问题

这样方法,相信大家看的也难受,因为问题确实是比较多。

1.一个字段进入,可能要经过三种判断(空值,规则,长度)。如果只是一个简单的电话号码规则校验,就要经过其他两种没必要的校验,造成不必要的开销。运行的流程就如同下面。

图片描述

2.规则校验里面,只有这几种校验,如果要增加其他校验,比如增加一个日期的规则,无法完成。如果一直修改源码,可能会导致函数巨大。

3.写法不优雅,调用也不方便。

3-3.代替方案

针对上面2-2的三个问题,逐个进行改善。

因为调用方式就不方便,很难在不改变validateForm调用方式的同时,优化重构内部的代码,又增加扩展性。重写这个方法又不可能,因为有个别的地方已经使用了这个API,自己一个一个的改不现实,所以就不修改这个validateForm,新建一个新的API:validate。在以后的项目上,也尽量引导同事放弃validateForm,使用新的API。

上面第一个,优化校验规则,每次校验(比如空值,长度,规则),都是一个简单的校验,不再执行其他没必要的校验。运行流程如同下面。

图片描述

let validate = function (arr) {let ruleData = {/*** @description 不能为空* @param val* @param msg* @return {*}*/isNoNull(val, msg){if (!val) {return msg}},/*** @description 最小长度* @param val* @param length* @param msg* @return {*}*/minLength(val, length, msg){if (val.toString().length length) {return msg}},/*** @description 是否是手机号码格式* @param val* @param msg* @return {*}*/isMobile(val, msg){if (!/^1[3-9]\d{9}$/.test(val)) {return msg}}}let ruleMsg, checkRule, _rule;for (let i = 0, len = arr.length; i };
let testData = {name: '',phone: '18819522663',pw: 'asda'
}
//校验函数调用
console.log(validate([{//校验的数据el: testData.phone,//校验的规则rules: [{rule: 'isNoNull', msg: '电话不能为空'}, {rule: 'isMobile', msg: '手机号码格式不正确'}]},{el: testData.pw,rules: [{rule: 'isNoNull', msg: '电话不能为空'},{rule:'minLength:6',msg:'密码长度不能小于6'}]}
]));

如果又有其它的规则,又得改这个,这样就违反了开放-封闭原则。如果多人共用这个函数,规则可能会很多,ruleData会变的巨大,造成不必要的开销。比如A页面有金额的校验,但是只有A页面有。如果按照上面的方式改,在B页面也会加载金额的校验规则,但是根本不会用上,造成资源浪费。

所以下面应用开放-封闭原则。给函数的校验规则增加扩展性。在实操之前,大家应该会懵,因为一个函数,可以进行校验的操作,又有增加校验规则的操作。一个函数做两件事,就违反了单一原则。到时候也难维护,所以推荐的做法就是分接口做。如下写法。

let validate = (function () {let ruleData = {/*** @description 不能为空* @param val* @param msg* @return {*}*/isNoNull(val, msg){if (!val) {return msg}},/*** @description 最小长度* @param val* @param length* @param msg* @return {*}*/minLength(val, length, msg){if (val.toString().length length) {return msg}},/*** @description 是否是手机号码格式* @param val* @param msg* @return {*}*/isMobile(val, msg){if (!/^1[3-9]\d{9}$/.test(val)) {return msg}}}return {/*** @description 查询接口* @param arr* @return {*}*/check: function (arr) {let ruleMsg, checkRule, _rule;for (let i = 0, len = arr.length; i })();
//校验函数调用-测试用例
console.log(validate.check([{//校验的数据el: testData.mobile,//校验的规则rules: [{rule: 'isNoNull', msg: '电话不能为空'}, {rule: 'isMobile', msg: '手机号码格式不正确'}]},{el: testData.password,rules: [{rule: 'isNoNull', msg: '电话不能为空'},{rule:'minLength:6',msg:'密码长度不能小于6'}]}
]));
//扩展-添加日期范围校验
validate.addRule('isDateRank',function (val,msg) {if(new Date(val[0]).getTime()>=new Date(val[1]).getTime()){return msg;}
});
//测试新添加的规则-日期范围校验
console.log(validate.check([{el:['2017-8-9 22:00:00','2017-8-8 24:00:00'],rules:[{rule:'isDateRank',msg:'日期范围不正确'}]}]));

如上代码所示,这里需要往ruleData添加日期范围的校验,这里可以添加。但是不能访问和修改ruleData的东西,有一个保护的作用。还有一个就是,比如在A页面添加日期的校验,只在A页面存在,不会影响其它页面。如果日期的校验在其它地方都可能用上,就可以考虑,在全局里面为ruleData添加日期的校验的规则。

至于第三个问题,这样的想法,可能不算太优雅,调用也不是太方便,但是就我现在能想到的,这个就是最好方案啊了。

这个看似是已经做完了,但是大家可能觉得有一种情况没能应对,比如下面这种,做不到。

clipboard.png

因为上面的check接口,只要有一个错误了,就立马跳出了,不会校验下一个。如果要实现下面的功能,就得实现,如果有一个值校验错误,就记录错误信息,继续校验下一个,等到所有的校验都执行完了之后,如下面的流程图。

图片描述

代码上面(大家先忽略alias这个属性)

let validate= (function () {let ruleData = {/*** @description 不能为空* @param val* @param msg* @return {*}*/isNoNull(val, msg){if (!val) {return msg}},/*** @description 最小长度* @param val* @param length* @param msg* @return {*}*/minLength(val, length, msg){if (val.toString().length length) {return msg}},/*** @description 是否是手机号码格式* @param val* @param msg* @return {*}*/isMobile(val, msg){if (!/^1[3-9]\d{9}$/.test(val)) {return msg}}}return {check: function (arr) {//代码不重复展示,上面一部分},addRule:function (type,fn) {//代码不重复展示,上面一部分},/*** @description 校验所有接口* @param arr* @return {*}*/checkAll: function (arr) {let ruleMsg, checkRule, _rule,msgArr=[];for (let i = 0, len = arr.length; i 0?msgArr:false;}}
})();
let testData = {name: '',phone: '188',pw: 'asda'
}
//扩展-添加日期范围校验
validate.addRule('isDateRank',function (val,msg) {if(new Date(val[0]).getTime()>=new Date(val[1]).getTime()){return msg;}
});
//校验函数调用
console.log(validate.checkAll([{//校验的数据el: testData.phone,alias:'mobile',//校验的规则rules: [{rule: 'isNoNull', msg: '电话不能为空'}, {rule: 'isMobile', msg: '手机号码格式不正确'},{rule:'minLength:6',msg: '手机号码不能少于6'}]},{el: testData.pw,alias:'pwd',rules: [{rule: 'isNoNull', msg: '电话不能为空'},{rule:'minLength:6',msg:'密码长度不能小于6'}]},{el:['2017-8-9 22:00:00','2017-8-8 24:00:00'],rules:[{rule:'isDateRank',msg:'日期范围不正确'}]}
]));

看到结果,现在所有的不合法的数据的记录都返回回来了。至于当时alias现在揭晓用处。
比如页面是vue渲染的,根据alias可以这样处理。

clipboard.png

图片描述

如果是jQuery渲染的,根据alias可以这样处理。

图片描述

图片描述

3-4.向下兼容方案

因为项目之前有使用了以前的校验API,不能一道切,在以前的API没废弃之前,不能影响之前的使用。所以要重写以前的validateForm,使之兼容现在的新API:validate。

let validateForm=function (arr) {let _param=[],_single={};for(let i=0;i

4.小结

今天的例子就到这里了,这个例子,无非就是给API增加扩展性。这个例子比较简单,不算难。大家用这个代码在浏览器上运行,就很好理解。如果大家对这个例子有什么更好的建议,或者代码上有什么问题,欢迎在评论区留言,大家多交流,相互学习。

-------------------------华丽的分割线--------------------

想了解更多,关注关注我的微信公众号:守候书阁

clipboard.png



推荐阅读
  • 本文介绍了在Vue项目中如何结合Element UI解决连续上传多张图片及图片编辑的问题。作者强调了在编码前要明确需求和所需要的结果,并详细描述了自己的代码实现过程。 ... [详细]
  • 用Vue实现的Demo商品管理效果图及实现代码
    本文介绍了一个使用Vue实现的Demo商品管理的效果图及实现代码。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文介绍了在使用vue和webpack进行异步组件按需加载时可能出现的报错问题,并提供了解决方法。同时还解答了关于局部注册组件和v-if指令的相关问题。 ... [详细]
  • Ihavethefollowingonhtml我在html上有以下内容<html><head><scriptsrc..3003_Tes ... [详细]
  • 在编写业务代码时,常常会遇到复杂的业务逻辑导致代码冗长混乱的情况。为了解决这个问题,可以利用中间件模式来简化代码逻辑。中间件模式可以帮助我们更好地设计架构和代码,提高代码质量。本文介绍了中间件模式的基本概念和用法。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • 本文介绍了在wepy中运用小顺序页面受权的计划,包含了用户点击作废后的从新受权计划。 ... [详细]
  • 网址:https:vue.docschina.orgv2guideforms.html表单input绑定基础用法可以通过使用v-model指令,在 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • 随着前端技术的发展,越来越多的开发者开始使用react、vue等web框架,但很少有人深入理解这些框架的源码。然而,这些框架底层都是由原生的javascript构建而成。对于初学前端的人来说,可能会认为javascript很容易上手,但实际上只是因为它被高度封装了。与能够使用封装类的人相比,能够理解框架原理的人则处于另一个层面。本文将深入剖析jquery源码,探寻框架底层的原理,帮助读者更好地理解web框架的运行机制。 ... [详细]
  • 本文介绍了在满足特定条件时如何在输入字段中使用默认值的方法和相应的代码。当输入字段填充100或更多的金额时,使用50作为默认值;当输入字段填充有-20或更多(负数)时,使用-10作为默认值。文章还提供了相关的JavaScript和Jquery代码,用于动态地根据条件使用默认值。 ... [详细]
author-avatar
用户um940d5n0q
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有