1. 程式人生 > 其它 >js中的策略模式

js中的策略模式

策略模式

策略模式的定義:定義一系列的演算法,把它們一個個封裝起來,並使它們可以互相替換

簡單來說就是我要到某個地方去旅遊,到目的地的過程有很多:飛機,高鐵,汽車。。。這些方法都作為一個封裝,等我要出發時只需要選擇一個方法就可以去了,則就是策略模式。

所以策略模式的關鍵就是封裝不同的方法,然後呼叫其中一個。

以表單驗證為例:

  • 該表單接收名字、郵箱、密碼三個輸入
  • 名字必須輸入且不能超過四個長度
  • 郵箱可選輸入但郵箱必須要含有@
  • 密碼必須輸入且長度不能少於三個

這是沒有使用策略模式的驗證:

btn.addEventListener("click", function () {
    if (!uname.value) {
        alert("要輸入名字");
        return;
    }
    if (uname.value.length > 4) {
        alert("名字太長");
        return;
    }
    if (email.value && !email.value.match(/@/g)) {
        alert("郵箱格式不正確");
        return;
    }
    if (!pwd.value) {
        alert("要輸入密碼");
        return;
    }
    if (pwd.value.length < 3) {
        alert("密碼太短");
        return;
    }
    //todo 提交表單
});

可以看到如果某天我要把郵箱的驗證換成更復雜的,密碼長度改成不少於4,都會進入該函式中修改,不符合違反開放封閉原則

接下來可以用策略模式的思想把判斷的過程提取出來:

methods = {
    isEmpty(element, errMsg) {
        element.value ?? alert(errMsg);
    },
    isLonger(element, length, errMsg) {
        element.value.length >= length || alert(errMsg);
    },
    isShorter(element, length, errMsg) {
        element.value.length < length || alert(errMsg);
    },
    isEmail(element, regexp, errMsg) {
        element.value.match(regexp) || alert(errMsg);
    },
};

現在把每個判斷都分拆為一個物件方法,接下來只要把方法對應到要判斷的元素上,因為一個元素可能會同時運用多個策略,所以還能用一個包裝類來收集:

methods = {
    notEmpty(element, errMsg) {
        return element.value || (alert(errMsg), "error");
    },
    canEmpty(element) {
        return element.value ? true : "break";
    },
    noLonger(element, length, errMsg) {
        return element.value.length < length || (alert(errMsg), "error");
    },
    noShorter(element, length, errMsg) {
        return element.value.length > length || (alert(errMsg), "error");
    },
    isEmail(element, regexp, errMsg) {
        return element.value.match(regexp) || (alert(errMsg), "error");
    },
};

class Check {
    constructor() {
        this.element = new Map(); //? 用來收集每個元素應用的策略
    }
    put(element, methodName, ...arg) {
        if (this.element.has(element)) {
            this.element.get(element).push({ methodName, args: arg });
        } else {
            this.element.set(element, [{ methodName, args: arg }]);
        }
    }
    start() {
        //? 迭代收集到的資料
        for (const obj of this.element) {
            check: {
                for (const aaa of obj[1]) {
                    //? 如果有錯誤會返回false,然後跳出現在的迴圈
                    switch (methods[aaa.methodName](obj[0], ...aaa.args)) {
                        case "break":
                            break check;
                        case "error":
                            return false;
                    }
                }
            }
        }

        return true;
    }
}

const checkInput = new Check();
checkInput.put(uname, "notEmpty", "名字不能為空");
checkInput.put(uname, "noLonger", 4, "名字太長");
checkInput.put(email, "canEmpty");
checkInput.put(email, "isEmail", /@/g, "郵箱不正確");
checkInput.put(pwd, "notEmpty", "密碼不能為空");
checkInput.put(pwd, "noShorter", 3, "密碼太短");

btn.addEventListener("click", function () {
    if (checkInput.start()) {
        //todo 提交
    }
});

現在雖然整個程式碼執行過程變得更復雜,但是在實現時不需要關注程式碼邏輯,直接新增現有的方法即可,每個方法都有可複用性,同時在method上也能新增或修改策略方法。