javascript設計模式實踐之策略模式--輸入驗證

策略模式中的策略就是一種算法或者業務規則,將這些策略作爲函數進行封裝,並向外提供統一的調用執行。

先定義一個簡單的輸入表單:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <style>
            .form{
                width: 400px;
                height: 200px;
                #margin: 0px auto;
            }

            .form-item-label{
                width:100px;
                text-align: right;
                float: left;
            }

            .form-item-input{
                float: left;
            }

            .form-item{
                width: 100% ;
                height: 50px;
                line-height: 50px;
            }
        </style>
    </head>
    <body>

        <div class='form'>
            <div class="form-item">
                <div class='form-item-label'><span>用戶名:</span></div>
                <div class='form-item-input'><input id='userName' name='用戶名' type="text"></div>
            </div>

            <div class="form-item" >
                <div class='form-item-label'><span>密碼:</span></div>
                <div class='form-item-input'><input id='password' name='密碼' type="text"></div>
            </div>

            <div class="form-item" >
                <div class='form-item-label'><span>確認密碼:</span></div>
                <div class='form-item-input'><input id='repassword' name='密碼確認' type="text"></div>
            </div>

            <div class="form-item" >
                <div class='form-item-label'><span>郵箱:</span></div>
                <div class='form-item-input'><input id='mail' name='郵箱' type="text" ></div>
            </div>
        </div>

        <br>
        <button id='submit' >提交</button>

        <script type='text/javascript' src="../reference/jquery-1.11.3.min.js"></script>
    </body>
</html>


一般在頁面上編輯信息後的提交動作中,都需要對輸入的信息進行驗證,會看到把很多負責check的代碼寫在提交函數中或者寫在一個獨立的check函數中。

比如像下面這樣。

            $(document).ready(function(){
                $('#submit').bind('click', doSubmit);
            });

            function doSubmit(){
                var eleUserName = document.getElementById('userName');
                if(eleUserName.value === '') {
                    alert('用戶名不能爲空');
                    return;
                }

                if(eleUserName.length < 6) {
                    alert('用戶名長度不能少於6個字符');
                    return;
                }

                if(eleUserName.length > 6) {
                    alert('用戶名長度不能多於20個字符');
                    return;
                }

            }

這樣的寫法功能上肯定能滿足要求,但是,會存在幾個問題:

1.如果我要在其他頁面上使用,那就要將代碼進行復制,所謂的複用就變成了複製,代碼會存在大量重複。好一點的會把check代碼分類整理封裝,單還會存在較多的重複複製。

2.如果我要增加一個輸入驗證,那麼就要直接修改提交函數,該函數會顯的臃腫,並且是破壞“開閉”原則的。

3.如果修改了提交函數,就要將函數設計的測試全都覆蓋一遍,因爲,不知道何時就會發生誤改或者未知的情況。

 

改造步驟:

1.將每個驗證邏輯看成是一個驗證策略並封裝成每個驗證策略函數,函數參數保持一致,可以接受dom元素,被驗證的值,錯誤消息,定製參數。

2.定義驗證器,可將驗證策略函數導入,也可以添加。

3.驗證器提供驗證方法,用於驗證時的調用,其內部調用具體的驗證策略函數。

4.驗證調用。

 

步驟1.

把每一個if都看成一種校驗的業務規則,把每種業務規則作爲一個單獨的策略函數,將所有的策略函數封裝成一個策略對象。

            var validationStrategies = {
                isNoEmpty: function(element, errMsg, value) {
                    if(value === '') {
                        return this.buildInvalidObj(element, errMsg, value );
                    }
                },

                minLength: function(element, errMsg, value, length) {
                    if(value.length < length){
                        return this.buildInvalidObj(element, errMsg, value);
                    }
                },

                maxLength: function(element, errMsg, value, length) {
                    if(value.length > length){
                        return this.buildInvalidObj(element, errMsg, value);
                    }
                },

                isMail: function(element, errMsg, value, length) {
                    var reg = /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/;
                    if(!reg.test(value)){
                        return this.buildInvalidObj(element, errMsg, value);
                    }
                }
            };

所有函數的參數的前3個都保持一致,而且是必須的,表示被驗證的DOM元素,錯誤消息,被驗證的值,第4個開始由函數自身的驗證規則決定定製的參數,可有多個參數。

“buildInvalidObj”方法只是把前3個參數打成一個錯誤對象進行返回,只要驗證不通過就會返回這個錯誤對象。

根據依賴倒置原則,高層次的模塊不應該依賴於低層次的模塊,因此不能讓驗證的調用方直接使用。

通過驗證器的方式進行封裝和抽象。

 

步驟2:

定義驗證器,可以將所有驗證策略導入其內,也可以單獨添加驗證策略函數。

            //輸入驗證器
            function InputValidators(){
                this.validators = [];
                this.strategies = {};
            }

            //從策略對象導入驗證策略函數
            //參數:
            //  strategies: 包含各種策略函數的對象
            InputValidators.prototype.importStrategies = function(strategies) {
                for(var strategyName in strategies) {
                    this.addValidationStrategy(strategyName, strategies[strategyName]);
                }
            };

            //添加驗證策略函數
            //參數:
            //  name: 策略名稱
            //  strategy: 策略函數
            InputValidators.prototype.addValidationStrategy = function(name, strategy){
                this.strategies[name] = strategy;
            };


步驟3:

添加驗證方法,接受外部調用。

第一個參數rule,設置成驗證規則,比如 "minLength:6",通過下面的代碼會生成對具體策略函數的調用,調用會壓到緩存中,等待一起調用。

":6"表示策略函數根據自身規則所定製的參數。

            //添加驗證方法
            //參數:
            //  rule: 驗證策略字符串
            //  element: 被驗證的dom元素
            //  errMsg: 驗證失敗時顯示的提示信息
            //  value: 被驗證的值
            InputValidators.prototype.addValidator = function(rule, element, errMsg, value) {
                var that = this;
                var ruleElements = rule.split(":");

                this.validators.push(function() {
                    var strategy = ruleElements.shift();
                    var params = ruleElements;
                    params.unshift(value);
                    params.unshift(errMsg);
                    params.unshift(element);

                    return that.strategies[strategy].apply(that, params);
                });
            };

通過一個check函數來調用所有的驗證。並將錯誤的結果進行返回。

            //開始驗證
            InputValidators.prototype.check = function() {
                for(var i = 0, validator; validator = this.validators[i++];){
                    var result = validator();
                    if(result) {
                        return result;
                    }
                }
            };


步驟4:

在需要驗證的地方,先new一個驗證器對象。

                var validators = new  InputValidators();

將包含驗證策略函數的對象導入,或者單獨添加驗證策略函數。

                validators.importStrategies(validationStrategies);

                validators.addValidationStrategy('isEqual',  function(element, errMsg, value1, value2) {
                    if(value1 !== value2) {
                        return this.buildInvalidObj(element, errMsg, value1 );
                    }
                });

可以看出,不同的驗證策略我們可以預先封裝進策略對象中,也可以根據實際情況即時添加。

然後通過添加驗證方法將需要驗證的策略,被驗證的dom元素,錯誤消息,被驗證的值添加進驗證器中,這樣避免了直接調用策略對象,降低了耦合性。

var eleUserName = document.getElementById('userName');
validators.addValidator('isNoEmpty', eleUserName, '用戶名不能爲空', eleUserName.value);
validators.addValidator('minLength:6', eleUserName, '用戶名的字符個數必須是6到20個', eleUserName.value);
validators.addValidator('maxLength:20', eleUserName, '用戶名的字符個數必須是6到20個', eleUserName.value);

var elePassword = document.getElementById('password');
validators.addValidator('isNoEmpty', elePassword, '密碼不能爲空', elePassword.value);
validators.addValidator('minLength:6', elePassword, '密碼的字符個數必須是6到20個', elePassword.value);
validators.addValidator('maxLength:20', elePassword, '密碼的字符個數必須是6到20個', elePassword.value);

var eleRepassword = document.getElementById('repassword');
validators.addValidator('isNoEmpty', eleRepassword, '確認密碼不能爲空', eleRepassword.value);
validators.addValidator('minLength:6', eleRepassword, '確認密碼的字符個數必須是6到20個', eleRepassword.value);
validators.addValidator('maxLength:20', eleRepassword, '確認密碼的字符個數必須是6到20個', eleRepassword.value);
validators.addValidator('isEqual:' + elePassword.value, eleRepassword, '兩次密碼不一致', eleRepassword.value);

var eleMail = document.getElementById('mail');
validators.addValidator('isNoEmpty', eleMail, '郵箱不能爲空', eleMail.value);
validators.addValidator('isMail', eleMail, '郵箱不是一個有效的格式', eleMail.value);

調用驗證器的check執行所有的驗證。

                var result = validators.check();
                if(result){
                    alert(result.errMsg);
                    result.element.focus();
                    result.element.select();
                    return false;
                }

check返回的是錯誤對象,我們可以在check後通過該對象統一地對DOM元素進行提示性操作,比如設置焦點,選中內容,或者爲輸入框外部包上一層紅色的樣式。


至此,可以看出通過策略模式的改造,輸入驗證時,我們只需要關心用哪個驗證規則,採用什麼樣的提示性信息即可,不再暴露實現細節,方便調用,方便後續的擴展和組件化。

 

全部代碼:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <style>
            .form{
                width: 400px;
                height: 200px;
                #margin: 0px auto;
            }

            .form-item-label{
                width:100px;
                text-align: right;
                float: left;
            }

            .form-item-input{
                float: left;
            }

            .form-item{
                width: 100% ;
                height: 50px;
                line-height: 50px;
            }
        </style>
    </head>
    <body>

        <div class='form'>
            <div class="form-item">
                <div class='form-item-label'><span>用戶名:</span></div>
                <div class='form-item-input'><input id='userName' name='用戶名' type="text"></div>
            </div>

            <div class="form-item" >
                <div class='form-item-label'><span>密碼:</span></div>
                <div class='form-item-input'><input id='password' name='密碼' type="text"></div>
            </div>

            <div class="form-item" >
                <div class='form-item-label'><span>確認密碼:</span></div>
                <div class='form-item-input'><input id='repassword' name='密碼確認' type="text"></div>
            </div>

            <div class="form-item" >
                <div class='form-item-label'><span>郵箱:</span></div>
                <div class='form-item-input'><input id='mail' name='郵箱' type="text" ></div>
            </div>
        </div>

        <br>
        <button id='submit' >提交</button>


        <script type='text/javascript' src="../reference/jquery-1.11.3.min.js"></script>
        <script type='text/javascript'>
            $(document).ready(function(){
                $('#submit').bind('click', doSubmit);
            });
function doSubmit(){
                var validators = new  InputValidators();

                validators.importStrategies(validationStrategies);

                validators.addValidationStrategy('isEqual',  function(element, errMsg, value1, value2) {
                    if(value1 !== value2) {
                        return this.buildInvalidObj(element, errMsg, value1 );
                    }
                });

                var eleUserName = document.getElementById('userName');
                validators.addValidator('isNoEmpty', eleUserName, '用戶名不能爲空', eleUserName.value);
                validators.addValidator('minLength:6', eleUserName, '用戶名的字符個數必須是6到20個', eleUserName.value);
                validators.addValidator('maxLength:20', eleUserName, '用戶名的字符個數必須是6到20個', eleUserName.value);

                var elePassword = document.getElementById('password');
                validators.addValidator('isNoEmpty', elePassword, '密碼不能爲空', elePassword.value);
                validators.addValidator('minLength:6', elePassword, '密碼的字符個數必須是6到20個', elePassword.value);
                validators.addValidator('maxLength:20', elePassword, '密碼的字符個數必須是6到20個', elePassword.value);

                var eleRepassword = document.getElementById('repassword');
                validators.addValidator('isNoEmpty', eleRepassword, '確認密碼不能爲空', eleRepassword.value);
                validators.addValidator('minLength:6', eleRepassword, '確認密碼的字符個數必須是6到20個', eleRepassword.value);
                validators.addValidator('maxLength:20', eleRepassword, '確認密碼的字符個數必須是6到20個', eleRepassword.value);
                validators.addValidator('isEqual:' + elePassword.value, eleRepassword, '兩次密碼不一致', eleRepassword.value);

                var eleMail = document.getElementById('mail');
                validators.addValidator('isNoEmpty', eleMail, '郵箱不能爲空', eleMail.value);
                validators.addValidator('isMail', eleMail, '郵箱不是一個有效的格式', eleMail.value);


                var result = validators.check();
                if(result){
                    alert(result.errMsg);
                    result.element.focus();
                    result.element.select();
                    return false;
                }

                alert('驗證通過');
            }

            //輸入驗證器
            function InputValidators(){
                this.validators = [];
                this.strategies = {};

                //this.from(validationStrategies);
            }

            //添加驗證方法
            //參數:
            //  rule: 驗證策略字符串
            //  element: 被驗證的dom元素
            //  errMsg: 驗證失敗時顯示的提示信息
            //  value: 被驗證的值
            InputValidators.prototype.addValidator = function(rule, element, errMsg, value) {
                var that = this;
                var ruleElements = rule.split(":");

                this.validators.push(function() {
                    var strategy = ruleElements.shift();
                    var params = ruleElements;
                    params.unshift(value);
                    params.unshift(errMsg);
                    params.unshift(element);

                    return that.strategies[strategy].apply(that, params);
                });
            };

            //添加驗證策略函數
            //參數:
            //  name: 策略名稱
            //  strategy: 策略函數
            InputValidators.prototype.addValidationStrategy = function(name, strategy){
                this.strategies[name] = strategy;
            };

            //從策略對象導入驗證策略函數
            //參數:
            //  strategies: 包含各種策略函數的對象
            InputValidators.prototype.importStrategies = function(strategies) {
                for(var strategyName in strategies) {
                    this.addValidationStrategy(strategyName, strategies[strategyName]);
                }
            };

            //驗證失敗時,將相關的錯誤信息打包返回
            //參數:
            //  element: dom元素
            //   errMsg: 驗證失敗時的提示消息
            //    value: 被驗證的值
            InputValidators.prototype.buildInvalidObj = function(element, errMsg, value){
                return {
                    'value': value,
                    'element': element,
                    'errMsg': errMsg
                };
            };

            //開始驗證
            InputValidators.prototype.check = function() {
                for(var i = 0, validator; validator = this.validators[i++];){
                    var result = validator();
                    if(result) {
                        return result;
                    }
                }
            };

            //驗證策略對象,包含默認的驗證策略函數
            var validationStrategies = {
                isNoEmpty: function(element, errMsg, value) {
                    if(value === '') {
                        return this.buildInvalidObj(element, errMsg, value );
                    }
                },

                minLength: function(element, errMsg, value, length) {
                    if(value.length < length){
                        return this.buildInvalidObj(element, errMsg, value);
                    }
                },

                maxLength: function(element, errMsg, value, length) {
                    if(value.length > length){
                        return this.buildInvalidObj(element, errMsg, value);
                    }
                },

                isMail: function(element, errMsg, value, length) {
                    var reg = /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/;
                    if(!reg.test(value)){
                        return this.buildInvalidObj(element, errMsg, value);
                    }
                }
            };
        </script>
    </body>
</html>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章