专栏名称: 守候i
web前端开发
目录
相关文章推荐
51好读  ›  专栏  ›  守候i

重构 - 用各种方式优化自己的函数库

守候i  · 掘金  · 前端  · 2018-06-11 00:42

正文

重构 - 用各种方式优化自己的函数库

有时候,我会想:比我优秀的人,比我更努力。我努力有什么用。但是现在我习惯反过来想这句话,别人为什么会比我优秀,就是因为别人比我更努力。与其拼天赋,更不如比行动。

1.前言

最近有几天时间空闲,也是在学怎么写更有可读性的代码,更简单,方便的API。简单来说就是重构方面的内容。今天简单分享下,对以前一个小项目( ecDo ,欢迎大家star)的API重构方式,下面的的代码如无说明,都是选取自我的项目中这个文件: ec-do-3.0.0-beta.1.js 中的 ecDo 这个对象(针对不同的重构目的,只列举1-3个代表实例,不一一列出)。如果大家有什么更好的方式,也欢迎在评论区留下您的建议。

首先说明一点,重构大家不要为重构而重构,要有目的重构。下面的改动,都是针对我原来的实现方式,更换更好的实现方式。主要会涉及在日常开发上,频繁使用的三个设计原则(单一职责原则,开放-封闭原则,最少知识原则),关于API设计的原则,不止三个。还有里式替换原则,依赖倒置原则等,但是这几个日常开发上没有感觉出来,所以这里就不多说了。 然后就是,虽然这几个带有‘原则’的字样,但是这些原则只是一个建议,指导的作用,没有哪个原则是必须要遵守的,在开发上,是否应该,需要遵守这些原则,具体情况,具体分析。

2.单一职责原则

这部分内容,主要就是有些函数,违反了单一职责原则。这样潜在的问题,可能会造成函数巨大,逻辑混乱,导致代码难以维护等。

2-1.getCount

在以前的版本,对这个函数的定义是:返回数组(字符串)出现最多的几次元素和出现次数。

原来实现的方案

/**
 * @description 降序返回数组(字符串)每个元素的出现次数
 * @param arr 待处理数组
 * @param rank 长度 (默认数组长度)
 * @param ranktype 排序方式(默认降序)
 */
getCount(arr, rank, ranktype) {
    let obj = {}, k, arr1 = []
    //记录每一元素出现的次数
    for (let i = 0, len = arr.length; i < len; i++) {
        k = arr[i];
        if (obj[k]) {
            obj[k]++;
        } else {
            obj[k] = 1;
        }
    }
    //保存结果{el-'元素',count-出现次数}
    for (let o in obj) {
        arr1.push({el: o, count: obj[o]});
    }
    //排序(降序)
    arr1.sort(function (n1, n2) {
        return n2.count - n1.count
    });
    //如果ranktype为1,则为升序,反转数组
    if (ranktype === 1) {
        arr1 = arr1.reverse();
    }
    let rank1 = rank || arr1.length;
    return arr1.slice(0, rank1);
},

调用方式

//返回值:el->元素,count->次数
ecDo.getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2])
//默认情况,返回所有元素出现的次数
//result:[{"el":"2","count":6},{"el":"1","count":4},{"el":"3","count":2},{"el":"4","count":1},{"el":"5","count":1},{"el":"6","count":1}]


ecDo.getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2],3)
//传参(rank=3),只返回出现次数排序前三的
//result:[{"el":"2","count":6},{"el":"1","count":4},{"el":"3","count":2}]

ecDo.getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2],null,1)
//传参(ranktype=1,rank=null),升序返回所有元素出现次数
//result:[{"el":"6","count":1},{"el":"5","count":1},{"el":"4","count":1},{"el":"3","count":2},{"el":"1","count":4},{"el":"2","count":6}]

ecDo.getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2],3,1)
//传参(rank=3,ranktype=1),只返回出现次数排序(升序)前三的
//result:[{"el":"6","count":1},{"el":"5","count":1},{"el":"4","count":1}]

这样目前是没有问题,但是这个函数承担了三个职责。统计次数,处理长度,排序方式。而且,处理长度和排序方式,有其他的原生处理方式,在这里写感觉有些鸡肋。

所以,重构这个API,就只保留统计次数这个职。至于长度和排序,有很多方式处理,slice,splice,length,sort等API或者属性都可以处理。

/**
 * @description 降序返回数组(字符串)每个元素的出现次数
 * @param arr
 * @return {Array}
 */
getCount(arr) {
    let obj = {}, k, arr1 = []
    //记录每一元素出现的次数
    for (let i = 0, len = arr.length; i < len; i++) {
        k = arr[i];
        if (obj[k]) {
            obj[k]++;
        } else {
            obj[k] = 1;
        }
    }
    //保存结果{el-'元素',count-出现次数}
    for (let o in obj) {
        arr1.push({el: o, count: obj[o]});
    }
    //排序(降序)
    arr1.sort(function (n1, n2) {
        return n2.count - n1.count
    });
    return arr1;
},

3.开放-封闭原则

3-1.checkType

checkType 检测字符串类型。以前的实现方式是。

/**
 * @description 检测字符串
 * @param str 待处理字符串
 * @param type 待检测的类型
 */
checkType(str, type) {
    switch (type) {
        case 'email':
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
        case 'mobile':
            return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
        case 'tel':
            return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
        case 'number':
            return /^[0-9]$/.test(str);
        case 'english':
            return /^[a-zA-Z]+$/.test(str);
        case 'text':
            return /^\w+$/.test(str);
        case 'chinese':
            return /^[\u4E00-\u9FA5]+$/.test(str);
        case 'lower':
            return /^[a-z]+$/.test(str);
        case 'upper':
            return /^[A-Z]+$/.test(str);
        default:
            return true;
    }
},

调用方式

ecDo.checkType('165226226326','mobile');
//result:false

因为 165226226326 不是一个有效的电话格式,所以返回false。但是这样会存在一个问题就是,如果以后我想加什么检测的规则呢?比如增加一个密码的规则。密码可以报错大小写字母,数字,点和下划线。上面的方案,就是只能在增加一个case。这样改违反了开放-封闭原则,而且这样会存在什么问题,我在之前讲策略模式的时候,已经提及,这里不重复。

所以我的做法就是,给它增加扩展性。

/**
 * @description 检测字符串
 */
checkType:(function(){
    let rules={
        email(str){
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
        },
        mobile(str){
            return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
        },
        tel(str){
            return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
        },
        number(str){
            return /^[0-9]$/.test(str);
        },
        english(str){
            return /^[a-zA-Z]+$/.test(str);
        },
        text(str){
            return /^\w+$/.test(str);
        },
        chinese(str){
            return /^[\u4E00-\u9FA5]+$/.test(str);
        },
        lower(str){
            return /^[a-z]+$/.test(str);
        },
        upper(str){
            return /^[A-Z]+$/.test(str);
        }
    };
    return {
        /**
        * @description 检测接口
        * @param str 待处理字符串
        * @param type 待检测的类型
        */
        check(str, type){
            return rules[type]?rules[type](str):false;
        },
        /**
        * @description 添加规则扩展接口
        * @param type 规则名称
        * @param fn 处理函数
        */
        addRule(type,fn){
            rules[type]=fn;
        }
    }
})(),

调用方式

console.log(ecDo.checkType.check('165226226326','mobile'));//false
ecDo.checkType.addRule('password',function (str) {
    return /^[-a-zA-Z0-9._]+$/.test(str);
})
console.log(ecDo.checkType.check('***asdasd654zxc','password'));//false

调用麻烦了一些,但是扩展性有了,以后面对新的需求可以更灵活的处理。

4.最少知识原则

最少知识原则,官方一点的解释是:一个对象应当对其他对象有尽可能少的了解。在下面表现为:尽可能的让用户更简单,更方便的使用相关的API。具体表现看下面的例子







请到「今天看啥」查看全文