zoukankan      html  css  js  c++  java
  • 重构

     

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

    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 /^(0d{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 /^(0d{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。具体表现看下面的例子

    4-1.trim

    以前 trim 函数实现方式

    /**
     * @description 大小写切换
     * @param str 待处理字符串
     * @param type 去除类型(1-所有空格  2-左右空格  3-左空格 4-右空格)
     */
    trim(str, type) {
        switch (type) {
            case 1:
                return str.replace(/s+/g, "");
            case 2:
                return str.replace(/(^s*)|(s*$)/g, "");
            case 3:
                return str.replace(/(^s*)/g, "");
            case 4:
                return str.replace(/(s*$)/g, "");
            default:
                return str;
        }
    }
    复制代码

    调用方式

    //去除所有空格
    ecDo.trim('  1235asd',1);
    //去除左空格
    ecDo.trim('  1235 asd ',3);
    复制代码

    这样的方式存在有目共睹,代表 type 参数的1,2,3,4可以说是一个神仙数,虽然对于开发者而言,知道是什么。但是如果有其他人使用,那么这样的 API 就增加了记忆成本和调用的复杂性。

    为了解决这个问题,处理方式就分拆 API 。

    /**
     * @description 清除左右空格
     */
    trim(str) {
        return str.replace(/(^s*)|(s*$)/g, "");
    },
    /**
     * @description 清除所有空格
     */
    trimAll(str){
        return str.replace(/s+/g, "");
    },
    /**
     * @description 清除左空格
     */
    trimLeft(str){
        return str.replace(/(^s*)/g, "");
    },
    /**
     * @description 清除右空格
     */
    trimRight(str){
        return str.replace(/(s*$)/g, "");
    }
    复制代码

    调用方式

    //去除所有空格
    ecDo.trim('  123 5asd');
    //去除左空格
    ecDo.trimLeft('  1235 asd ');
    复制代码

    这样 API 多了,但是记忆成本和调用简单了。

    4-2.encryptStr

    下面的 API 在简单使用方便,表现得更为突出

    原来方案

    /**
     * @description 加密字符串
     * @param str 字符串
     * @param regArr 字符格式
     * @param type 替换方式
     * @param ARepText 替换的字符(默认*)
     */
    encryptStr(str, regArr, type = 0, ARepText = '*') {
        let regtext = '',
            Reg = null,
            replaceText = ARepText;
        //repeatStr是在上面定义过的(字符串循环复制),大家注意哦
        if (regArr.length === 3 && type === 0) {
            regtext = '(\w{' + regArr[0] + '})\w{' + regArr[1] + '}(\w{' + regArr[2] + '})'
            Reg = new RegExp(regtext);
            let replaceCount = this.repeatStr(replaceText, regArr[1]);
            return str.replace(Reg, '$1' + replaceCount + '$2')
        }
        else if (regArr.length === 3 && type === 1) {
            regtext = '\w{' + regArr[0] + '}(\w{' + regArr[1] + '})\w{' + regArr[2] + '}'
            Reg = new RegExp(regtext);
            let replaceCount1 = this.repeatStr(replaceText, regArr[0]);
            let replaceCount2 = this.repeatStr(replaceText, regArr[2]);
            return str.replace(Reg, replaceCount1 + '$1' + replaceCount2)
        }
        else if (regArr.length === 1 && type === 0) {
            regtext = '(^\w{' + regArr[0] + '})'
            Reg = new RegExp(regtext);
            let replaceCount = this.repeatStr(replaceText, regArr[0]);
            return str.replace(Reg, replaceCount)
        }
        else if (regArr.length === 1 && type === 1) {
            regtext = '(\w{' + regArr[0] + '}$)'
            Reg = new RegExp(regtext);
            let replaceCount = this.repeatStr(replaceText, regArr[0]);
            return str.replace(Reg, replaceCount)
        }
    },
    复制代码

    调用方式

    ecDo.encryptStr('18819322663',[3,5,3],0,'+')
    //result:188+++++663
    ecDo.encryptStr('18819233362',[3,5,3],1,'+')
    //result:+++19233+++
    ecDo.encryptStr('18819233362',[5],0)
    //result:*****233362
    ecDo.encryptStr('18819233362',[5],1)
    //result:"188192*****"
    复制代码

    这个 API 存在的问题也是一样,太多的神仙数,比如[3,5,3],1,0等。相对于4-1的例子,这个对使用这造成的记忆成本和调用复杂性更大。甚至很容易会搞晕。如果是阅读源码,if-else的判断,别说是其他人了,就算是我这个开发者,我都会被搞蒙。

    处理这些问题,也类似4-1。拆分 API 。

    /**
     * @description 加密字符串
     * @param regIndex 加密位置  (开始加密的索引,结束加密的索引)
     * @param ARepText 加密的字符 (默认*)
     */
    encryptStr(str, regIndex, ARepText = '*') {
        let regtext = '',
            Reg = null,
            _regIndex=regIndex.split(','),
            replaceText = ARepText;
        //repeatStr是在上面定义过的(字符串循环复制),大家注意哦
        _regIndex=_regIndex.map(item=>+item);
        regtext = '(\w{' + _regIndex[0] + '})\w{' + (1+_regIndex[1]-_regIndex[0]) + '}';
        Reg = new RegExp(regtext);
        let replaceCount = this.repeatStr(replaceText, (1+_regIndex[1]-_regIndex[0]));
        return str.replace(Reg, '$1' + replaceCount);
    },
     /**
     * @description 不加密字符串
     * @param regIndex 不加密位置  (开始加密的索引,结束加密的索引)
     * @param ARepText 不加密的字符 (默认*)
     */
    encryptUnStr(str, regIndex, ARepText = '*') {
        let regtext = '',
            Reg = null,
            _regIndex=regIndex.split(','),
            replaceText = ARepText;
        _regIndex=_regIndex.map(item=>+item);
        regtext = '(\w{' + _regIndex[0] + '})(\w{' + (1+_regIndex[1]-_regIndex[0]) + '})(\w{' + (str.length-_regIndex[1]-1) + '})';
        Reg = new RegExp(regtext);
        let replaceCount1 = this.repeatStr(replaceText, _regIndex[0]);
        let replaceCount2 = this.repeatStr(replaceText, str.length-_regIndex[1]-1);
        return str.replace(Reg, replaceCount1 + '$2' + replaceCount2);
    },
    /**
     * @description 字符串开始位置加密
     * @param regIndex 加密长度
     * @param ARepText 加密的字符 (默认*)
     */
    encryptStartStr(str,length,replaceText = '*'){
        let regtext = '(\w{' + length + '})';
        let Reg = new RegExp(regtext);
        let replaceCount = this.repeatStr(replaceText, length);
        return str.replace(Reg, replaceCount);
    },
    /**
     * @description 字符串结束位置加密
     * @param regIndex 加密长度
     * @param ARepText 加密的字符 (默认*)
     */
    encryptEndStr(str,length,replaceText = '*'){
        return this.encryptStartStr(str.split('').reverse().join(''),length,replaceText).split('').reverse().join('');
    },
    复制代码

    调用方式

    console.log(`加密字符 ${ecDo.encryptStr('18819233362','3,7','+')}`)
    //result:188+++++362
    console.log(`不加密字符 ${ecDo.encryptUnStr('18819233362','3,7','+')}`)
    //result:+++19233+++
    console.log(`字符串开始位置加密 ${ecDo.encryptStartStr('18819233362','4')}`)
    //result:****9233362
    console.log(`字符串结束位置加密 ${ecDo.encryptEndStr('18819233362','4')}`)
    //result:1881923****
    复制代码

    结果一样,但是调用就比之前简单了,也不需要记忆太多东西。

    类似4-1和4-2的改动还有几个实例,在这里就不列举了!

    4-3.cookie

    这个实例与上面两个实例不太一样,上面两个 API 为了简化使用,把一个 API 拆分成多个,但是这个 API 是把多个 API 合并成一个。

    /**
     * @description 设置cookie
     * @param name cookie名称
     * @param value 值
     * @param iDay 有效时间(天数)
     */
    setCookie(name, value, iDay) {
        let oDate = new Date();
        oDate.setDate(oDate.getDate() + iDay);
        document.cookie = name + '=' + value + ';expires=' + oDate;
    },
    /**
     * @description 获取cookie
     * @param name cookie名称
     */
    getCookie(name) {
        let arr = document.cookie.split('; '),arr2;
        for (let i = 0; i < arr.length; i++) {
            arr2 = arr[i].split('=');
            if (arr2[0] == name) {
                return arr2[1];
            }
        }
        return '';
    },
    /**
     * @description 删除cookie
     * @param name cookie名称
     */
    removeCookie(name) {
        this.setCookie(name, 1, -1);
    },
    复制代码

    调用方式

    ecDo.setCookie(cookieName,'守候',1)//设置(有效时间为1天)
    ecDo.getCookie(cookieName)//获取
    ecDo.removeCookie(cookieName)//删除
    复制代码

    新增API

    /**
     * @description 操作cookie
     * @param name cookie名称
     * @param value 值
     * @param iDay 有效时间(天数)
     */
    cookie(name, value, iDay){
        if(arguments.length===1){
            return this.getCookie(name);
        }
        else{
            this.setCookie(name, value, iDay);
        }
    },
    复制代码

    调用方式

    ecDo.cookie(cookieName,'守候',1)//设置
    ecDo.cookie(cookieName)//获取
    ecDo.cookie(cookieName,'守候',-1)//删除(中间的值没有意义了,只要cookie天数设置了-1,就会删除。)
    复制代码

    这样调用,使用方法的记忆成本增加了,但是不需要记3个API,只需要记一个。

    5.代码优化

    5-1.checkPwdLevel

    原来方案

    /**
     * @description 检测密码强度
     */
    checkPwdLevel(str) {
        let nowLv = 0;
        if (str.length < 6) {
            return nowLv
        }
        if (/[0-9]/.test(str)) {
            nowLv++
        }
        if (/[a-z]/.test(str)) {
            nowLv++
        }
        if (/[A-Z]/.test(str)) {
            nowLv++
        }
        if (/[.|-|_]/.test(str)) {
            nowLv++
        }
        return nowLv;
    },
    复制代码

    调用方式

    console.log(ecDo.checkPwdLevel('asd188AS19663362_.'));
    //4
    复制代码

    这样写没问题,但是想必大家和我一样,看到if有点多,而且if为true的时候,做的事情还是一样的,就忍不住要折腾了。就有了下面的方案。

    /**
     * @description 检测密码强度
     */
    checkPwdLevel(str) {
        let nowLv = 0;
        if (str.length < 6) {
            return nowLv
        }
        //把规则整理成数组,再进行循环判断
        let rules=[/[0-9]/,/[a-z]/,/[A-Z]/,/[.|-|_]/];
        for(let i=0;i<rules.length;i++){
            if(rules[i].test(str)){
                nowLv++;
            }
        }
        return nowLv;
    },
    复制代码

    这样写,处理的事情是一样的,性能方面可以忽略不计,但是看着舒服。

    5-2.upsetArr

    原来方案

    /**
     * @description 数组顺序打乱
     * @param arr
     */
    upsetArr(arr) {
        return arr.sort(() => {
             return Math.random() - 0.5
        });
    },
    复制代码

    调用方式

    ecDo.upsetArr([1,2,3,4,5,6,7,8,9]);
    复制代码

    这种方式没错,但是有个遗憾的地方就是不能实现完全乱序,就是乱的不够均匀。所以换了一种方式。

    /**
     * @description 数组顺序打乱
     * @param arr
     * @return {Array.<T>}
     */
    upsetArr(arr) {
        let j,_item;
        for (let i=0; i<arr.length; i++) {
            j = Math.floor(Math.random() * i);
            _item = arr[i];
            arr[i] = arr[j];
            arr[j] = _item;
        }
        return arr;
    },
    复制代码

    原理就是遍历数组元素,然后将当前元素与以后随机位置的元素进行交换,这样乱序更加彻底。

  • 相关阅读:
    bzoj 1051: [HAOI2006]受欢迎的牛
    bzoj 1192: [HNOI2006]鬼谷子的钱袋
    一些动规水题
    USACO 2014 Open Silver Fairphoto
    USACO 2013 Nov Silver Pogo-Cow
    09day1
    09day2
    08day2
    08day1
    07day2
  • 原文地址:https://www.cnblogs.com/web-chuanfa/p/9304712.html
Copyright © 2011-2022 走看看