定義
定義一系列的演演算法,將他們一個個封裝起來,使他們直接可以相互替換。
- 演演算法:就是寫的邏輯可以是你任何一個功能函式的邏輯
- 封裝:就是把某一功能點對應的邏輯給抽出來
- 可替換:建立在封裝的基礎上,這些獨立的演演算法可以很方便的替換
通俗的理解就是,把你的演演算法(邏輯)封裝到不同的策略中,在不同的策略中是互相獨立的,這樣我們封裝的每一個演演算法是可以很方便的複用。
策略模式主要解決在有多種情況下,使用 if...else
所帶來的複雜和難以維護。
它的優點是演演算法可以自由切換,同時可以避免多重if...else
判斷,且具有良好的擴充套件性。
看一個真實場景--最簡單的策略模式
我們有一個根據不同的型別返回不同價格的一個方法
function getPrice (type) {
if (type === 1) {
// code 或許每個分支要處理很多邏輯
}
if (type === 2) {
// code
}
if (type === 3) {
// code
}
if (type === 4) {
// code
}
if (type === 5) {
// code
}
if (type === 6) {
// code
}
if (type === 7) {
// code
}
}
從程式碼上看確實沒有什麼問題,但是如果需要增加一種新的型別,我們就會一個if判斷,導致這個方法可能會過於龐大,後期不太好維護。
其次程式碼每次都是從上往下走,可能存在前面某個判斷出現問題(如某個 && 的判斷變數null 之類),導致後面的程式碼沒有走,所以這種影響還是挺大的。
用了這麼多 if-else
,我們的目的是什麼?是不是就是為了把傳進來的引數的值-對應的處理函式
,這個對映關係給明確下來?在 JS 中我們可以透過物件對映的形式來做,如下程式碼
/*
1、把 if else 的程式碼快最佳化為一個一個的對映
2、把if else 裡面的邏輯抽離成一個獨立的函式,這樣方便其他模組或者分支使用
*/
function getPrice (type) {
const actionMap = {
'1': action1,
'2': action2,
'3': action3,
'4': action4,
'5': action5,
'6': action6,
'7': action7,
}
const params = {}
return actionMap[type](params)
}
這種程式碼結構變得易讀、易維護。
這就是最簡單的策略模式
模擬表單校驗邏輯
如果不把邏輯封裝起來,那麼我們在判斷的時候會寫很多的if else,如寫一個很簡單的表單的校驗
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="telephone=no,email=no" name="format-detection">
<meta name="App-Config" content="fullscreen=yes,useHistoryState=yes,transition=yes">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<div>
<form action="" id="form">
姓名:<input type="text" id="username"><br>
密碼:<input type="password" id="password1"><br>
確認密碼:<input type="password" id="password2"><br>
手機號:<input type="text" id="phone"><br>
<input type="submit" value="提交">
</form>
</div>
<script>
function getValue (id) {
return document.getElementById(id).value;
}
var formData = document.getElementById('form')
formData.onsubmit = function () {
var name = getValue('username');
var pwd1 = getValue('password1');
var pwd2 = getValue('password2');
var tel = getValue('phone');
if (name.replace(/(^\s*)|(\s*$)/g, "") === "") {
alert('使用者名稱不能為空')
return false
}
if (pwd1.replace(/(^\s*)|(\s*$)/g, "") === "") {
alert('密碼不能為空')
return false
}
if (pwd2.replace(/(^\s*)|(\s*$)/g, "") === "") {
alert('確認密碼不能為空')
return false
}
if (pwd2 !== pwd1) {
alert('確認密碼與原密碼不相同!')
return false
}
if (tel.replace(/(^\s*)|(\s*$)/g, "") === "") {
alert('手機號碼不能為空')
return false
}
if (!/^1[3,4,5,7,8,9][0-9]\d{8}$/.test(tel)) {
alert('手機號碼格式不正確')
return false
}
alert('註冊成功')
}
</script>
</body>
</html>
只是4個欄位,我們用了 6個if判斷來做相關的邏輯校驗。
仔細觀察發現很多校驗的邏輯是一致的,所以我們可以把他封裝起來,用策略模式修改成如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="telephone=no,email=no" name="format-detection">
<meta name="App-Config" content="fullscreen=yes,useHistoryState=yes,transition=yes">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<div>
<form action="" id="form">
姓名:<input type="text" id="username"><br>
密碼:<input type="password" id="password1"><br>
確認密碼:<input type="password" id="password2"><br>
手機號:<input type="text" id="phone"><br>
<input type="submit" value="提交">
</form>
</div>
<script>
let formData = document.getElementById('form')
function getValue(id) {
return document.getElementById(id).value;
}
function Validate() { }
Validate.prototype.rules = {
// 是否手機號
isMobile: function (str) {
let rule = /^1[3,4,5,7,8,9][0-9]\d{8}$/;
return rule.test(str);
},
// 是否必填
isRequired: function (str) {
// 除去首尾空格
let value = str.replace(/(^\s*)|(\s*$)/g, "");
return value !== "";
},
// 最小長度
minLength: function (str, length) {
let strLength = str.length;
return strLength >= length;
},
// 是否相等
isEqual: function (...args) {
let equal = args.every(function (value) {
return value === args[0];
})
return equal;
}
}
Validate.prototype.test = function (rules) {
let _this = this;
let valid; // 儲存校驗結果
for (let key in rules) { // 遍歷校驗規則物件
for (let i = 0; i < rules[key].length; i++) { // 遍歷每一個欄位的校驗規則
let ruleName = rules[key][i].rule; // 獲取每一個校驗規則的規則名
let value = rules[key][i].value; // 獲取每一個校驗規則的校驗值
if (!Array.isArray(value)) { // 統一校驗值為陣列型別
value = new Array(value)
}
let result = _this.rules[ruleName].apply(this, value); // 呼叫校驗規則方法進行校驗
if (!result) { // 如果校驗不透過,就獲取校驗結果資訊,並立即跳出迴圈不再執行,節約消耗
valid = {
errValue: key,
errMsg: rules[key][i].message
}
break;
}
}
if (valid) { // 如果有了校驗結果,代表存在不透過的欄位,則立即停止迴圈,節約消耗
break;
}
}
return valid; // 把校驗結果反悔出去
}
formData.onsubmit = function () {
event.preventDefault()
let validator = new Validate();
let result = validator.test({
'username': [{ rule: 'isRequired', value: this.username.value, message: '使用者名稱不能為空!' }],
'password1': [
{ rule: 'isRequired', value: this.password1.value, message: '密碼不能為空!' },
{ rule: 'minLength', value: [this.password1.value, 6], message: '密碼長度不能小於6個字元!' }
],
'password2': [
{ rule: 'isRequired', value: this.password2.value, message: '確認密碼不能為空!' },
{ rule: 'minLength', value: [this.password2.value, 6], message: '確認密碼長度不能小於6個字元!' },
{ rule: 'isEqual', value: [this.password2.value, this.password1.value], message: '確認密碼與原密碼不相同!' }
],
'isMobile': [
{ rule: 'isRequired', value: this.phone.value, message: '手機號不能為空!' },
{ rule: 'isMobile', value: this.phone.value, message: '手機號格式不正確!' }
]
})
if (result) {
console.log(result);
} else {
console.log('校驗透過');
}
}
</script>
</body>
</html>
下次我們增加其他的欄位也只是增加規則而已,而不會去修改判斷的業務邏輯。
小結
- 將一個個演演算法封裝起來,提高程式碼複用率,減少程式碼冗餘
-
策略模式可看作為
if/else
判斷的另一種表現形式,在達到相同目的的同時,極大的減少了程式碼量以及程式碼維護成本 - 策略模式有效避免多重條件選擇語句,將演演算法封裝在策略中