你們好,這是第三篇做者對於設計模式的分享了,前兩篇能夠參考:javascript
手寫一下JavaScript的幾種設計模式 (工廠模式,單例模式,適配器模式,裝飾者模式,建造者模式)html
用英雄聯盟的方式講解JavaScript設計模式(一)! (構造函數模式,外觀模式,代理模式)前端
設計模式在編程開發中用途十分普遍,每個模式描述了一個在咱們周圍不斷重複發生的問題,以及解決問題的核心!不少的時候,對於咱們其實如何選擇適合的設計模式,才更加消耗時間。從以前的文章,每個設計模式都會有一到兩個例子,既能夠給本身之後開發回憶設計模式提供幫助,也但願能夠給讀者一些啓發。java
策略模式定義了算法家族,分別封裝起來,讓他們之間能夠互相替換,此模式讓算法的變化不會影響到使用算法的客戶。ajax
那聽起來雲山霧繞,怎麼都涉及到 算法 了 ?難道我一個前端是時候進攻算法大軍了嗎。其實並非,用一個超級常見的例子就能夠解釋!算法
讓咱們又回到英雄聯盟,當咱們第一次登錄英雄聯盟的時候,須要輸入一個新的姓名吧?起名規則起碼得有如下這幾條:編程
其中具體的設定,只有開發者才知道了,身爲玩家只能注意到這幾點,那策略模式怎麼體如今這裏的呢?首先咱們實現一個顯而易見功能的例子:設計模式
var validator = {
validate: function (value, type) {
switch (type) {
case 'isNonEmpty ':
{
return true; // 名字不能爲空
}
case 'isNoNumber ':
{
return true; // 名字 不是 純數字
break;
}
case 'isExist ':
{
return true; // 名字已存在
}
case 'isLength':
{
return true; // 長度合理
}
}
}
};
複製代碼
上述代碼能夠實現一個表單驗證系統,剛建立角色起名字的時候驗證那裏的功能,只須要傳入相應的參數就能夠。數組
validator.validate('測試名字', 'isNumber') // false
bash
雖然能夠獲得理想的結果,但這種寫法有十分嚴重的缺點,最重要的,每次增長或修改規則時,須要修改整個validate
函數,這不符合開放封閉原則,增長邏輯,讓函數更加複雜不可控。
那真正適合的代碼應該怎麼寫呢?
var validator = {
// 全部驗證規則處理函數存放的地方
types: {},
validate: function (str, types) {
this.messages = [];
var checker, result, msg, i;
for (i in types) {
var type = types[i];
checker = this.types[type]; // 獲取驗證規則的驗證類
if (!checker) { // 若是驗證規則類不存在,拋出異常
throw {
name: "ValidationError",
message: "No handler to validate type " + type
};
}
result = checker.validate(str); // 使用查到到的單個驗證類進行驗證
if (!result) {
msg = "Invalid value for *" + type + "*, " + checker.instructions;
this.messages.push(msg);
}
}
return this.hasErrors();
},
// 是否有message錯誤信息
hasErrors: function () {
return this.messages.length !== 0;
}
};
複製代碼
上面的代碼定義了validator
對象以及validate
函數,函數內部會對傳入的字符串,檢測類型數組進行處理。若是存在規則,進行判斷,並把錯誤信息發送到this.message
。若是不存在規則,天然的就不須要繼續執行,拋出error
便可。
// 驗證給定的值是否不爲空
validator.types.isNonEmpty = {
validate: function (value) {
return value !== "";
},
instructions: "傳入的值不能爲空"
};
// 驗證給定的值是否 不是 純數字
validator.types.isNoNumber = {
validate: function (value) {
return isNaN(value); // 僞寫法,由於isNaN會誤判布爾值和空字符串等,所以並不能做爲真正判斷純數字的依據
},
instructions: "傳入的值不能是純數字"
};
// 驗證給定的值是否存在
validator.types.isExist = {
validate: function (value) {
// $.ajax() ...
return true;
},
instructions: "給定的值已經存在"
};
// 驗證給定的值長度是否合理
validator.types.isLength = {
validate: function (value) {
var l = value.toString().length
if ( l > 2 && l < 10) {
return true;
} else {
return false;
}
},
instructions: "長度不合理,請長度在2-10個字符內"
};
複製代碼
上面對types
規則進行了補充,定義了幾種規則,至此,對於名稱校驗,簡單的設定就敲完了。接下來要準備的就是一個可以在英雄聯盟合理的名字進行驗證:
var types = ['isExist', 'isLength', 'isNoNumber', 'isNonEmpty']; // 決定想要的規則,不管增長或者減小,原函數都不須要改動
function check (name, types) {
validator.validate(name, types);
if (validator.hasErrors()) {
console.log(validator.messages.join("\n"));
} else {
console.log('驗證經過!')
}
}
check('okckokckokck', types) // 長度不合理,請長度在2-10個字符內
check('老faker', types) // true
check('00001', types) // 傳入的值不能是純數字
複製代碼
首先設定好想要的規則,用一個types
數組囊括進來,以後定義一個check
函數,把結果處理封裝一下,最後傳入參數,不管想要檢測什麼規則,都不須要修改原函數。如今不管我想檢測faker
可不能夠註冊,仍是一個空字符串,均可以傳入規則,進行使用。若是想添加新的規則,只須要在validator.types
上續寫對象就能夠,方便清晰,結構明朗。
核心思想就是把複雜的算法結構,分別封裝起來,讓他們之間能夠互相替換,上面的代碼就很好的體現了 互相替換 ,由於不管我怎麼去修改想要的規則,都不須要改動本來的代碼。
在系統沿着多個維度變化的同時,又不增長其複雜度並已達到解耦。將抽象部分與它的實現部分分離,使它們均可以獨立地變化。簡單的說:橋接模式最主要的特色是實現層(如元素綁定的事件)與抽象層(如修飾頁面UI邏輯)解耦分離。
下面依然是一個例子:
假如咱們還在英雄聯盟的世界裏,每一場遊戲最終都會有一個結局,不管勝利仍是失敗,都會彈出一個窗口,告訴你 —— Victory或者是Defeat。
function GameMessage (type) { // 抽象 與 實現 的 橋樑
this.fn = type ? new Victory() : new Defeat()
}
GameMessage.prototype.show = function() {
this.fn.show()
}
function Defeat() { // 抽象層
this.show = function() {
console.log('im loser')
}
}
function Victory() { // 抽象層
this.show = function() {
console.log('im winner')
}
}
// 實現層
function getResult() {
var switchVD = Math.ceil(Math.random()*10) > 5 // 勝利失敗一半一半
return new GameMessage(switchVD)
}
var result1 = getResult()
var result2 = getResult()
var result3 = getResult()
result1.show()
result2.show()
result3.show()
複製代碼
首先咱們建立了一個GameMessage
的函數,咱們都知道勝利失敗都有一半的機率,所以定義了switchVD
變量,模擬一個隨機事件,同時每次結果調用一次getResult
函數,獲取最新結果。
橋接模式體如今GameMessage
函數上,將抽象的 Victory()
以及 Defeat()
與 咱們獲取結果的 getResult()
實現解耦。函數之間不糅合邏輯,但又經過橋樑函數,鏈接在一塊兒。
這麼寫的好處就是,二者均可以獨立的變化,互不打擾。畢竟若是揉在一塊兒,可能邏輯以下:
function Defeat() { // 抽象層
this.show = function() {
console.log('im loser')
}
}
function Victory() { // 抽象層
this.show = function() {
console.log('im winner')
}
}
var switchVD = Math.ceil(Math.random()*10) > 5
if (switchVD) {
var result = new Victory()
} else {
var result = new Defeat()
}
result.show() // loser or winner
複製代碼
上述代碼能夠輕鬆的看到,若是沒有橋接模式,直接把實現層,渲染層糅合在一塊兒,會依賴上下文。假若獲取不到上下文的環境,很容易出現問題。
橋接模式在平常開發中,會在不經意間頻繁使用,目的也是爲了讓代碼結構清晰,將不一樣邏輯的代碼互相解耦。便於往後維護,開發時也更能區分模塊,看的舒服,天然效率也高。
橋接模式關鍵是要理解抽象部分與實現部分的分離,使得兩者能夠獨立的變化,而沒必要拘泥於形式。靈活的變化,適用場景的多變就很是適合使用這種模式來實現。橋接模式最重要的是找到代碼中不一樣的變化緯度。
狀態模式(State)容許一個對象在其內部狀態改變的時候改變它的行爲,對象看起來彷佛修改了它的類。 其實就是用一個對象或者數組記錄一組狀態,每一個狀態對應一個實現,實現的時候根據狀態挨個去運行實現。
優勢:
缺點:
好比下面咱們定義一個英雄的狀態,名字叫亞索,其中亞索可能同時有好幾個狀態好比 邊走邊攻擊 —— 咱們俗稱的「走A」,還有可能釋放技能以後接一個「B鍵回家」的操做,固然最有可能的是eqw閃r行雲流水的操做收穫一我的頭,再接一個ctrl+f6
等。
若是對這些操做一個個進行處理判斷,須要多個if-else
或switch
不只醜陋不說,並且在遇到有組合動做的時候,實現就會更爲冗餘。那麼咱們這裏的複雜操做,可使用 狀態模式 來實現。
狀態模式 的思路是:首先建立一個狀態對象或者數組,在對象內部存儲須要操做的狀態數組或對象,而後狀態對象提供一些接口,能夠更改狀態以及執行動做。
那如今有一個英雄叫作亞索!下面代碼,咱們就用亞索的狀態來實現一下傳說中的狀態模式:
function YasuoState() {
//存儲當前即將執行動做的狀態!
this.currentstate = [];
this.Actions = {
walk : function(){
console.log('walk');
},
attack : function(){
console.log('attack');
},
magic : function(){
console.log('magic');
},
backhome : function(){
console.log('backhome');
}
};
}
YasuoState.prototype.changeState = function() {
//清空當前的動做
this.currentstate = [];
Object.keys(arguments).forEach((i) => this.currentstate.push(arguments[i]))
return this;
}
YasuoState.prototype.YasuoActions = function() {
//當前動做集合中的動做依次執行
this.currentstate.forEach((k) => this.Actions[k] && this.Actions[k]())
return this;
}
var yasuoState = new YasuoState();
yasuoState.changeState('walk','attack').YasuoActions().changeState('walk').YasuoActions().YasuoActions();
複製代碼
上面代碼成功實現了亞索的狀態模式,咱們假設他有走路、攻擊、釋放技能、回家幾個狀態,其中這幾個狀態實際上是能夠同時輸入指令的,要否則那些職業選手的高光操做就會在 技能銜接 而出現的卡頓 香消玉殞。
狀態模式最多見的就是平常的例子 —— 紅綠燈,每當切換狀態的時候,執行一次動做。
至於英雄聯盟中,最多見的就是邊走邊攻擊,在輸入命令後,首先改變了咱們對象的狀態yasuoState.changeState('magic','backhome')
,而後由於在代碼中有return this;
,能夠鏈式調用接下來的行爲,因而咱們讓它依次執行剛纔輸入的狀態。接下來又一次改變了狀態changeState('walk')
,而且進行執行。能夠看到執行了兩次,因爲狀態並無再次改變,所以只須要重複執行就能夠保證咱們的英雄一直往前走下去了。
但願狀態模式能夠幫助你解決絕大多數,須要切換狀態的操做。遇到相似的問題時,能夠迅速拿出成熟可靠的狀態模式解決之。
本次分享的三種模式,均可以在英雄聯盟中找到影子,由於我喜歡這款遊戲,因此很輕鬆能夠找到其中使用的設計模式:
設計模式主要能夠幫助咱們解決,開發中對代碼的設計問題,那咱們如何找到合適的對象,並應用合適的設計模式呢?
借用書中的幾個提示吧:
尋找合適的對象
決定對象的粒度
決定好這個對象設計的接口
把對象須要的具體函數實現
合理的運用代碼複用機制
設計的代碼應該能夠支持變化,要對變化有預見性
大概是這幾種,在 javascript 中涉及編譯的場景較少,就不敘述了。
設計模式是軟件開發人員在軟件開發過程當中面臨的通常問題的解決方案。這些解決方案是衆多軟件開發人員通過至關長的一段時間的試驗和錯誤總結出來的。
根據上面的幾條規則,在開發接口和函數的時候,時刻注意,就能夠避免大多數代碼設計上的問題,對於之後的維護也會有巨大的幫助。下一個接受代碼的人,也會十分感激你的,讀代碼其實和讀書同樣,你如今偷懶寫的代碼可能無所謂,後面接手的人會瘋狂吐槽。相反若是你優雅的實現,像我,就會內心由衷的佩服,看到整齊的函數,註釋明朗的功能,不得不說,高手確實是高手啊,短短 200 行,讓人跪服,就突出一個詞 —— 優雅。