JavaScript设计模式之“策略模式”(二)

        策略模式指的是定义一些列的算法,并且把他们封装起来。这篇文章我们继续接着上篇文章继续探讨。从定义上来看,策略模式就是用来封装算法的,但如果策略模式仅仅是用来封装算法的,未免有点大材小用。在实际开发中,我们通常将这个“算法”的概念扩散开来,使用策略模式也能用来封装一些列的业务规则。只要这些业务规则指向一致,并且可以被替换使用,我们就可以用策略模式来封装他们。

        这篇文章我们就来用表单校验的例子来深化我们对策略模式的理解

一个web项目中,注册、登录、修改用户信息等功能都离不开表单的提交,在一个用户输入数据交给后台之前,尝尝前端开发人员会对用户输入的数据加以校验,校验输入的数据是否符合规范,长度啊、格式啊等等,这样可以避免因为提交的数据存在问题而带来不必要的开销。

        那么好,问题来了,假设我们正在编写一个注册页面,在点击注册之前,有如下几条校验规则

  • 用户名不能为空
  • 密码长度不能小于6位
  • 手机号必须符合格式

未引入策略模式的“最笨方法”

<body>
    <div>
        请输入用户名:<input type="text" name="username"><br>
        请输入密码:<input type="text" name="password"><br>
        请输入手机号:<input type="text" name="phoneNumber"><br>
        <button class="btn">提交</button>
    </div>
    <script>
        const registerForm = document.getElementsByClassName("registerForm")
        const inputArr = document.querySelectorAll("input")
        const btn = document.querySelector(".btn")
        btn.onclick = function () {
            if (inputArr[0].value === "") {
                alert("用户名不能为空!")
            }
            if (inputArr[1].value.length < 6) {
                alert("密码长度不能小于6")
            }
            if (!/(^1[3|5|8][0-9]{9}$)/.test(inputArr[2].value)) {
                alert("手机号格式不正确")
            }
        }
    </script>
</body>

结果也显而易见,并不用我多说,这始终很常见的写法,想必大家也写过类似的代码,问题和上篇文章的计算奖金的“最笨方法”如出一辙,缺点也十分明显,

  • 触发的函数较为庞大,包含了很多if-else分支,这些分支要覆盖所有的规则
  • 函数缺乏弹性,如果要增加规则或者修改规则,就必须去函数内部修改代码
  • 函数复用性很差

下面我们用策略模式重构代码,将校验规则分别抽离封装到校验对象中,作为策略类将具体的业务初始化逻辑,包括参数传递,参数处理,规则调用,封装成另一个函数对象,最为环境类(Context)。直接上代码(可能环境类中处理参数和整体封装过程中有点繁琐,但是作为一个较为具体的业务场景,这种的复杂度是应该的,请仔细阅读)

<body>
    请输入用户名:<input type="text" name="username"><br>
    请输入密码:<input type="text" name="password"><br>
    请输入手机号:<input type="text" name="phoneNumber"><br>
    <button class="btn">提交</button>

    <script>
        // 封装策略对象
        const strategies = {
            isNonEmpty: function (value, errorMsg) {
                if (value === "") {
                    return errorMsg
                }
            },
            minLength: function (value, length, errorMsg) {
                if (value.length < length) {
                    return errorMsg
                }
            },
            isMobile: function (value, errorMsg) {
                if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
                    return errorMsg
                }
            }
        }

        // 构建环境类
        const Validator = function () {
            this.cache = []      // 用于保存校验规则
        }
        Validator.prototype.add = function (dom, rule, errorMsg) {
            const arr = rule.split(":")         // 将传入的规则和参数分开
            this.cache.push(function () {        // 将校验的步骤封装成函数,存入缓存(cache)
                let strategy = arr.shift()       // 拿到用户传入的规则名称
                arr.unshift(dom.value)           // 将输入的数值添加至参数列表
                arr.push(errorMsg)               // 将错误对象添加至参数列表 
                return strategies[strategy].apply(dom, arr)     // 返回对应规则调用结果
            })
        }
        // 规则校验启动入口
        Validator.prototype.start = function () {
            for (var i = 0; i < this.cache.length; i++) {
                let validatorFunc = this.cache[i]
                let msg = validatorFunc()
                if (msg) return msg
            }
        }

        // 实现类  具体实现的代码,不是核心代码,上方两个类为核心代码
        const volidataFunc = function () {
            const validator = new Validator()

            // 添加校验规则
            validator.add(inputArr[0], 'isNonEmpty', '用户名不能为空')
            validator.add(inputArr[1], 'minLength:6', '密码长度不能小于6位')
            validator.add(inputArr[2], 'isMobile', '手机号码格式不正确')

            console.log(validator.cache)
            let errorMsg = validator.start()
            return {errorMsg}
        }

        let inputArr = document.querySelectorAll("input")
        let btn = document.querySelector(".btn")
        btn.onclick = function () {
            let {errorMsg} = new volidataFunc()
            if (errorMsg) {
                alert(errorMsg)
                return false   // 阻止表单提交(这里并未用表单结构,大家看看就行)
            } else {
                alert("登录成功")
            }
        }
    </script>
</body>

使用策略模式重构代码后,我们仅仅可以只通过配置新的规则或者修改规则,然后将所需要的用到规则的节点传入规则就可以完成表单校验。嗯....其实这个版本以及可以满足大部分的表单校验了,但是,我们可以设想一下,如果一个输入项的校验不止一条规则怎么办?当然很简单,将数据项所对应的规则项里面多加判断呗,但是这样就又回到了之前的“最笨的写法”,最好的办法就是再加条规则,然后在校验时,用多条规则去校验所需的数据项。 那么我们需要怎么在哪改动代码呢?在添加规则的时候,添加多条规则吧,我想你应该也想到了,就是下方这样的写法(可以看成伪码)

        validator.add(dom , [
            {strategy: 'xxx' , errorMsg: 'xxx'},  // 规则一
            {strategy: 'xxx' , errorMsg: 'xxx'}   // 规则二
            ...
        ])

那么下面我们直接给出最终版本,没有注释哦,看看自己是否能理解,代码改动并不大

<body>
    请输入用户名:<input type="text" name="username"><br>
    请输入密码:<input type="text" name="password"><br>
    请输入手机号:<input type="text" name="phoneNumber"><br>
    <button class="btn">提交</button>

    <script>

        // 封装策略对象
        const strategies = {
            isNonEmpty: function (value, errorMsg) {
                if (value === "") {
                    return errorMsg
                }
            },
            minLength: function (value, length, errorMsg) {
                if (value.length < length) {
                    return errorMsg
                }
            },
            isMobile: function (value, errorMsg) {
                if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
                    return errorMsg
                }
            }
        }

        // 构建环境类
        const Validator = function () {
            this.cache = []      // 用于保存校验规则
        }
        Validator.prototype.add = function (dom, rules) {
            let self = this
            for (let i = 0; i < rules.length; i++) {
                let rule = rules[i];
                (function (rule) {
                    let arr = rule.strategy.split(":")
                    let errorMsg = rule.errorMsg
                    self.cache.push(function () {
                        let strategy = arr.shift()       // 拿到用户传入的规则名称
                        arr.unshift(dom.value)           // 将输入的数值添加至参数列表
                        arr.push(errorMsg)               // 将错误对象添加至参数列表 
                        return strategies[strategy].apply(dom, arr)     // 返回对应规则调用结果
                    })
                })(rule)
            }
        }
        // 规则校验启动入口
        Validator.prototype.start = function () {
            for (var i = 0; i < this.cache.length; i++) {
                let validatorFunc = this.cache[i]
                let msg = validatorFunc()
                if (msg) return msg
            }
        }

        let inputArr = document.querySelectorAll("input")
        let btn = document.querySelector(".btn")
        // 实现类
        const volidataFunc = function () {
            const validator = new Validator()

            // 添加校验规则
            validator.add(inputArr[0], [
                {
                    strategy: 'isNonEmpty',
                    errorMsg: '用户名不能为空'
                },
                {
                    strategy: 'minLength:10',
                    errorMsg: '用户名长度不能小于10'
                }
            ])

            validator.add(inputArr[1], [
                {
                    strategy: 'minLength:6',
                    errorMsg: '密码长度不能小于6'
                }
            ])

            validator.add(inputArr[2], [
                {
                    strategy: 'isMobile',
                    errorMsg: '手机号格式不正确'
                }
            ])


            console.log(validator.cache)
            let errorMsg = validator.start()
            return { errorMsg }
        }

        btn.onclick = function () {
            let { errorMsg } = new volidataFunc()
            if (errorMsg) {
                alert(errorMsg)
                return false   // 阻止表单提交(这里并未用表单结构,大家看看就行)
            } else {
                alert("登录成功")
            }
        }

    </script>
</body>

以上就是整个通过策略模式实现表单验证,还是优化程度很高的

策略模式的优点与缺点

  • 通过利用组合、委托和多态等技术和思想,可以避免多重条件选择的重复粘贴工作
  • 其对开放-封闭原则完美支持
  • 算法复用性强

当然策略模式也有缺点,但是并不严重,在使用策略模式时,同样是无法避免的会增加策略类的大小,而且分离与内容类外,代码可读性较差哈哈哈,书上是这么说的,其实大家可以不用太过多考虑这个缺点,代码的复用性以及效率的提高,有时候必然会牺牲一定的空间和代码可读性,这里其实就是作为一些套话,让大家知道这个缺点还有另一个缺点,属于核心缺点,会到最初的那个问题,出去旅行,我们需要怎么选择出行方式呢?当时我们的考虑是,根据自身的情况然后选择合适的方式吧,但是,我们必须得知道所有可能的路线,最后选择最优的那个方式吧,这就是策略模式的比较核心的缺点,我们不得不知道有哪些策略,最终再选其中某些策略去应用到内容中,最好的方式应该是,我们不需要知道里面总共有哪些策略,我们只需要提供内容,然后算法根据内容给出最优策略,这样应该是最优秀的业务解决方案,但是在策略模式中,这个问题是很难避免的,除非花费大量事件设计一个优秀的算法去匹配策略,这样就太麻烦了

其实策略模式已经在JavaScript这种高度函数化编码的语言中潜移默化的植入了,想必大家一定用过高阶函数吧,就是那种返回值是一个函数,或者传入的参数是一个函数的那种用法,是不是和策略模式很像呢,传入的函数参数就是一个策略,他可以通过不同的封装移植到不同的地方,不同的函数也可以返回不同的结果。

以上及时策略模式的所有了

内容摘自《JavaScript设计模式与开发实践》· 曾探 · 著

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青灯夜游/

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值