造函數(shù)模式
在JavaScript里,構(gòu)造函數(shù)通常是認(rèn)為用來(lái)實(shí)現(xiàn)實(shí)例的特殊的構(gòu)造函數(shù)。通過(guò)new關(guān)鍵字來(lái)調(diào)用定義的構(gòu)造函數(shù),你可以告訴JavaScript你要?jiǎng)?chuàng)建一個(gè)新對(duì)象并且新對(duì)象的成員聲明都是構(gòu)造函數(shù)里定義的。在構(gòu)造函數(shù)內(nèi)部,this關(guān)鍵字引用的是新創(chuàng)建的對(duì)象。
作為一個(gè)老聯(lián)盟fans,一定要親手實(shí)現(xiàn)一下設(shè)計(jì)模式也可以融會(huì)貫通。
現(xiàn)在打算創(chuàng)建一個(gè)英雄聯(lián)盟對(duì)象,需要地圖,英雄,士兵,野怪,還有開(kāi)始游戲的按鈕。
function LOL(maps, heros, soldier, monster) {
this.maps=maps
this.heros=heros
this.soldier=soldier
this.monster=monster
this.start=function() {
return '地圖:' + this.maps + '\n對(duì)戰(zhàn)英雄:' + this.heros.join() + '\n小兵類型:' + this.soldier + '\n野怪:' + this.monster + '\n'
}
}
var game1=new LOL('召喚師峽谷', ['影流之主', '詭術(shù)妖姬'], '超級(jí)兵', '紅buff')
var game2=new LOL('大亂斗', ['影流之主', '詭術(shù)妖姬'], '超級(jí)兵', '紅buff')
console.log(game1.start())
console.log(game2.start())
這樣寫代碼,每局游戲需要重新創(chuàng)建一個(gè)英雄聯(lián)盟實(shí)例,
這樣使用構(gòu)造器,有多少個(gè)game就需要多少個(gè)start函數(shù)方法,如果共用一個(gè)start方法,可以節(jié)約很多內(nèi)存
function LOL(maps, heros, soldier, monster) {
this.maps=maps
this.heros=heros
this.soldier=soldier
this.monster=monster
}
LOL.prototype.start=function() {
return '地圖:' + this.maps + '\n對(duì)戰(zhàn)英雄:' + this.heros.join() + '\n小兵類型:' + this.soldier + '\n野怪:' + this.monster + '\n'
}
var game1=new LOL('召喚師峽谷', ['影流之主', '詭術(shù)妖姬'], '超級(jí)兵', '紅buff')
var game2=new LOL('大亂斗', ['影流之主', '詭術(shù)妖姬'], '超級(jí)兵', '紅buff')
console.log(game1.start())
console.log(game2.start())
如果讓start方法變成大家通用的就好了,因此把LOL.prototype.start改寫,這樣所以的LOL實(shí)例就可以共用一個(gè)方法,從原型鏈上繼承即可
上面的方式可以節(jié)省內(nèi)存,start實(shí)例函數(shù)可以在所有LOL對(duì)象的實(shí)例中使用
如果不使用new 也可以有其他方式創(chuàng)建對(duì)象
function LOL(maps, heros, soldier, monster) {
this.maps=maps
this.heros=heros
this.soldier=soldier
this.monster=monster
this.start=function() {
return '地圖:' + this.maps + '\n對(duì)戰(zhàn)英雄:' + this.heros.join() + '\n小兵類型:' + this.soldier + '\n野怪:' + this.monster + '\n'
}
}
var game3=new Object();
LOL.call(game3, "扭曲叢林", ['影流之主', '劍圣'], '遠(yuǎn)程兵', '大龍');
console.log(game3.start())
//也可以不使用new ,通過(guò)call方法在game3的作用域調(diào)用LOL
這種方式雖然可以創(chuàng)建新的構(gòu)造函數(shù),但卻不能繼承LOL原型上的函數(shù)
如果直接運(yùn)行LOL()函數(shù)(不使用new的情況下),由于this指向的是window對(duì)象,因此start方法會(huì)變成window.start()
如果強(qiáng)制要求函數(shù)使用new 方法也可以如下創(chuàng)建:
function LOL(maps, heros, soldier, monster) {
if (!(this instanceof LOL)) {
return new LOL(maps, heros, soldier, monster);
}
this.maps=maps
this.heros=heros
this.soldier=soldier
this.monster=monster
}
通過(guò)判斷this的instanceof,就可以知道究竟是來(lái)自new方法,還是說(shuō)是直接調(diào)用。如果是直接調(diào)用的話,判斷條件為true,還是會(huì)return一個(gè)新的實(shí)例。
e.g:
var s=new String("lol");
var n=new Number(101);
var b=new Boolean(true);
// s n b返回的是實(shí)例對(duì)象
//String{
// 0: 'l',
// 1: 'o',
// 2: 'l'
// }
//可以直接給變量賦值,或者不加new 關(guān)鍵詞
外觀模式最大的體現(xiàn)其實(shí)就是入口,比如init()函數(shù),把一些內(nèi)部的函數(shù)都放在這個(gè)門面之下,只需要調(diào)用這個(gè)門面函數(shù),其他亂七八糟的功能都可以實(shí)現(xiàn)。
現(xiàn)在有一個(gè)英雄,叫做亞索,我希望給他一些配置,比如技能,衣服等
function YaSuo() {
}
function Qskill (hero) {
hero.prototype.Qskill=function() {
console.log('hasaki!!')
}
}
function Wskill (hero) {
hero.prototype.Wskill=function() {
console.log('風(fēng)墻')
}
}
function Eskill (hero) {
hero.prototype.Eskill=function() {
console.log('快樂(lè)')
}
}
function Rskill (hero) {
hero.prototype.Rskill=function() {
console.log('痛里唉該痛')
}
}
function Skin (hero, skin) {
hero.prototype.skin=skin
}
function CreateYasuo () {
Qskill(YaSuo)
Wskill(YaSuo)
Eskill(YaSuo)
Rskill(YaSuo)
Skin(YaSuo, 'originSkin')
return new YaSuo()
}
CreateYasuo()
// 創(chuàng)建成功 外觀模式啟動(dòng)
通過(guò)上面的代碼,成功創(chuàng)建了一個(gè)美麗的亞索。我們最后只需要了解,外觀模式不僅簡(jiǎn)化類中的接口,而且對(duì)接口與調(diào)用者也進(jìn)行了解耦。外觀模式經(jīng)常被認(rèn)為開(kāi)發(fā)者必備,它可以將一些復(fù)雜操作封裝起來(lái),并創(chuàng)建一個(gè)簡(jiǎn)單的接口用于調(diào)用。
說(shuō)白了就是用一個(gè)接口封裝其它的接口。
外觀模式優(yōu)點(diǎn)就是易使用。缺點(diǎn)則是,當(dāng)連續(xù)使用外觀模式創(chuàng)建的接口時(shí),可能會(huì)產(chǎn)生性能問(wèn)題。
e.g.
var addMyEvent=function (el, ev, fn) {
if (el.addEventListener) {
el.addEventListener(ev, fn, false);
} else if (el.attachEvent) {
el.attachEvent('on' + ev, fn);
} else {
el['on' + ev]=fn;
}
};
這是最常見(jiàn)對(duì)監(jiān)聽(tīng)事件的處理,前端必會(huì)。其中的addMyEvent就是對(duì)其他三個(gè)接口的封裝,產(chǎn)生了一個(gè)門面,也就是外觀模式。
其實(shí)代理模式我們生活中接觸的很多了。比如es6中的proxy對(duì)象,還有我們平時(shí)上網(wǎng)用的VPN。那其實(shí)代理模式,就是讓一個(gè)對(duì)象幫助其他的對(duì)象來(lái)做事。
比如我現(xiàn)在想創(chuàng)建一個(gè)英雄,名字叫做卡莉斯塔,俗稱滑板鞋。這個(gè)英雄有個(gè)特點(diǎn),當(dāng)她放R技能的時(shí)候,會(huì)把一個(gè)對(duì)象拉過(guò)來(lái)到自己身邊幾秒,代理這個(gè)對(duì)象的走路行為,禁止他釋放技能等等,那這就要用到代理模式了。
// 聲明走路動(dòng)作
function Walk (hero) { // 代理期間執(zhí)行的操作
return function() {console.log(hero + ' is walk')}
}
function Kalisita () { // proxy
this.walk=Walk('Kalisita')
this.Rskill=function(hero) { // 傳入要拉取的英雄
this.walk=function() {
Walk('Kalisita')() // 既需要自己走
hero.walk() // 還需要帶著人一起走
}
}
}
function HeroA () { // 被代理走路的英雄
this.walk=Walk('heroA')
}
var k=new Kalisita()
var a=new HeroA()
k.walk() // Kalisita is walk
a.walk() // heroA is walk
k.Rskill(a) // k把a(bǔ)的walk事件代理了, 現(xiàn)在k觸發(fā)walk的同時(shí),也會(huì)帶著a一起walk哦
k.walk()
// Kalisita is walk
// heroA is walk
代理模式主要用于幾點(diǎn),
詳情可以參考《大話設(shè)計(jì)模式》
而我們前端代碼中用的比較多的,應(yīng)該就是vue.js中對(duì)data中數(shù)據(jù)響應(yīng)式的代理。vue3中也將使用大量ES6支持的Proxy對(duì)象來(lái)改寫。
e.g.
通過(guò)代理,嘗試設(shè)置私有屬性
function getPrivateProps(obj, filterFunc) {
return new Proxy(obj, {
get(obj, prop) {
if (!filterFunc(prop)) {
let value=Reflect.get(obj, prop);
// 如果是方法, 將this指向修改原對(duì)象
if (typeof value==='function') {
value=value.bind(obj);
}
return value;
}
},
set(obj, prop, value) {
if (filterFunc(prop)) {
throw new TypeError(`Cant set property ${prop}`);
}
return Reflect.set(obj, prop, value);
},
has(obj, prop) {
return filterFunc(prop) ? false : Reflect.has(obj, prop);
},
ownKeys(obj) {
return Reflect.ownKeys(obj).filter(prop=> !filterFunc(prop));
},
getOwnPropertyDescriptor(obj, prop) {
return filterFunc(prop) ? undefined : Reflect.getOwnPropertyDescriptor(obj, prop);
}
});
}
function propFilter(prop) {
return prop.indexOf('_')===0;
}
本次分享了三種設(shè)計(jì)模式,分別為構(gòu)造函數(shù)模式,外觀模式,代理模式;都是日常開(kāi)發(fā)很常用的設(shè)計(jì)模式。
其中es6proxy可以訪問(wèn)阮老師博客查看詳細(xì)api,具體使用也可以借鑒vue源碼。代理模式雖然很好用,但也不是任何時(shí)候都建議使用,如果你的代碼需要獲得某些對(duì)象的權(quán)限,不妨可以使用一下代理模式。在比較簡(jiǎn)單的場(chǎng)景,可能就沒(méi)有必要了。
外觀模式是最常用的,畢竟每一個(gè)js文件總是需要一個(gè)入口的。無(wú)論是main函數(shù)還是init函數(shù),都是起到一個(gè)外觀包裝的作用。當(dāng)然外觀模式并不是必須作為一個(gè)文件入口存在,只要能把重復(fù)的代碼提煉出來(lái),就是一個(gè)合理的外觀模式。
構(gòu)造函數(shù)模式就不多說(shuō)了,簡(jiǎn)單好用。
復(fù)制代碼是危險(xiǎn)的。如果有兩段相同的代碼,幾乎可以說(shuō)一定是有問(wèn)題的,因?yàn)槊看胃膭?dòng),要維護(hù)兩段代碼
盡量減少IO操作,如操作數(shù)據(jù)庫(kù),網(wǎng)絡(luò)發(fā)送,甚至printf ,這些操作比直接操作內(nèi)存,慢很多倍、
修改Bug時(shí),一定要從最簡(jiǎn)單的基本的地方開(kāi)始檢查,不要檢查到最底層沒(méi)問(wèn)題,發(fā)現(xiàn)是傳入的某個(gè)參數(shù)是錯(cuò)的。先不要懷疑系統(tǒng)的部分。
設(shè)計(jì)架構(gòu),同時(shí)了解細(xì)節(jié),有些Bug,調(diào)起來(lái)可能費(fèi)時(shí)費(fèi)力,甚至花個(gè)二三天,其實(shí)當(dāng)時(shí)寫的時(shí)候,只要稍微注意,就可以輕松避免。避免Bug的代價(jià)與找出并修改Bug的代價(jià),實(shí)在是差太多了。
把一段長(zhǎng)代碼,分成很多小函數(shù),便于維護(hù),連自己都不愿看,不愿改的代碼,百分百有問(wèn)題。
寫程序時(shí),先把流程搞清楚。把各個(gè)流程用的函數(shù)寫清楚,函數(shù)可以留空,這樣編程就變成了填空題。
做新功能時(shí),把數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì),放在較重要的位置
希望這次分享能夠既有趣,又讓你得到收獲,謝謝閱讀。
源自:https://juejin.im/post/5ebe65a0f265da7bd76bb626
聲明:文章著作權(quán)歸作者所有,如有侵權(quán),請(qǐng)聯(lián)系小編刪除。
閩南網(wǎng)]
lol揮別2018起航2019活動(dòng)正式開(kāi)啟,不少玩家想知道召喚師圖標(biāo)免費(fèi)領(lǐng)取方法。
lol揮別2018起航2019活動(dòng)介紹:
揮別2018起航2019
活動(dòng)時(shí)間:即日起-1月14日
領(lǐng)取截止時(shí)間:2019年1月21日15:59
活動(dòng)入口:點(diǎn)擊進(jìn)入
圖標(biāo)領(lǐng)取地址:https://lol.qq.com/act/a20180929awards/index.html#box2
起航2019召喚師圖標(biāo)寶箱介紹:
1、一般活動(dòng)結(jié)束后10個(gè)工作日內(nèi)可在本頁(yè)面領(lǐng)取。
2、請(qǐng)?jiān)诮刂箷r(shí)間前領(lǐng)取道具,逾期作廢。
3、領(lǐng)取之前請(qǐng)先進(jìn)入一次游戲商城才能收到道具。
4、領(lǐng)取之后請(qǐng)重新進(jìn)入游戲查收道具。
關(guān)于lol揮別2018起航2019活動(dòng)的介紹就到這。
大家好,這是第三篇作者對(duì)于設(shè)計(jì)模式的分享了,前兩篇可以參考:
手寫一下JavaScript的幾種設(shè)計(jì)模式 (工廠模式,單例模式,適配器模式,裝飾者模式,建造者模式)
用英雄聯(lián)盟的方式講解JavaScript設(shè)計(jì)模式(一)! (構(gòu)造函數(shù)模式,外觀模式,代理模式)
設(shè)計(jì)模式在編程開(kāi)發(fā)中用途十分廣泛,每一個(gè)模式描述了一個(gè)在我們周圍不斷重復(fù)發(fā)生的問(wèn)題,以及解決問(wèn)題的核心!很多的時(shí)候,對(duì)于我們其實(shí)如何選擇適合的設(shè)計(jì)模式,才更加消耗時(shí)間。從之前的文章,每一個(gè)設(shè)計(jì)模式都會(huì)有一到兩個(gè)例子,既可以給自己以后開(kāi)發(fā)回憶設(shè)計(jì)模式提供幫助,也希望可以給讀者一些啟發(fā)。
策略模式定義了算法家族,分別封裝起來(lái),讓他們之間可以互相替換,此模式讓算法的變化不會(huì)影響到使用算法的客戶。
那聽(tīng)起來(lái)云山霧繞,怎么都涉及到 算法 了 ?難道我一個(gè)前端是時(shí)候進(jìn)攻算法大軍了嗎。其實(shí)并不是,用一個(gè)超級(jí)常見(jiàn)的例子就可以解釋!
讓我們又回到英雄聯(lián)盟,當(dāng)我們第一次登陸英雄聯(lián)盟的時(shí)候,需要輸入一個(gè)新的姓名吧?起名規(guī)則起碼得有以下這幾條:
其中具體的設(shè)定,只有開(kāi)發(fā)者才知道了,身為玩家只能注意到這幾點(diǎn),那策略模式怎么體現(xiàn)在這里的呢?首先我們實(shí)現(xiàn)一個(gè)顯而易見(jiàn)功能的例子:
var validator={
validate: function (value, type) {
switch (type) {
case 'isNonEmpty ':
{
return true; // 名字不能為空
}
case 'isNoNumber ':
{
return true; // 名字 不是 純數(shù)字
break;
}
case 'isExist ':
{
return true; // 名字已存在
}
case 'isLength':
{
return true; // 長(zhǎng)度合理
}
}
}
};
復(fù)制代碼
上述代碼可以實(shí)現(xiàn)一個(gè)表單驗(yàn)證系統(tǒng),剛創(chuàng)建角色起名字的時(shí)候驗(yàn)證那里的功能,只需要傳入相應(yīng)的參數(shù)就可以。
validator.validate('測(cè)試名字', 'isNumber') // false
雖然可以得到理想的結(jié)果,但這種寫法有十分嚴(yán)重的缺點(diǎn),最重要的,每次增加或修改規(guī)則時(shí),需要修改整個(gè)validate函數(shù),這不符合開(kāi)放封閉原則,增加邏輯,讓函數(shù)更加復(fù)雜不可控。
那真正適合的代碼應(yīng)該怎么寫呢?
var validator={
// 所有驗(yàn)證規(guī)則處理函數(shù)存放的地方
types: {},
validate: function (str, types) {
this.messages=[];
var checker, result, msg, i;
for (i in types) {
var type=types[i];
checker=this.types[type]; // 獲取驗(yàn)證規(guī)則的驗(yàn)證類
if (!checker) { // 如果驗(yàn)證規(guī)則類不存在,拋出異常
throw {
name: "ValidationError",
message: "No handler to validate type " + type
};
}
result=checker.validate(str); // 使用查到到的單個(gè)驗(yàn)證類進(jìn)行驗(yàn)證
if (!result) {
msg="Invalid value for *" + type + "*, " + checker.instructions;
this.messages.push(msg);
}
}
return this.hasErrors();
},
// 是否有message錯(cuò)誤信息
hasErrors: function () {
return this.messages.length !==0;
}
};
復(fù)制代碼
上面的代碼定義了validator對(duì)象以及validate函數(shù),函數(shù)內(nèi)部會(huì)對(duì)傳入的字符串,檢測(cè)類型數(shù)組進(jìn)行處理。如果存在規(guī)則,進(jìn)行判斷,并把錯(cuò)誤信息發(fā)送到this.message。如果不存在規(guī)則,自然的就不需要繼續(xù)執(zhí)行,拋出error即可。
// 驗(yàn)證給定的值是否不為空
validator.types.isNonEmpty={
validate: function (value) {
return value !=="";
},
instructions: "傳入的值不能為空"
};
// 驗(yàn)證給定的值是否 不是 純數(shù)字
validator.types.isNoNumber={
validate: function (value) {
return isNaN(value); // 偽寫法,因?yàn)閕sNaN會(huì)誤判布爾值和空字符串等,因此并不能作為真正判斷純數(shù)字的依據(jù)
},
instructions: "傳入的值不能是純數(shù)字"
};
// 驗(yàn)證給定的值是否存在
validator.types.isExist={
validate: function (value) {
// $.ajax() ...
return true;
},
instructions: "給定的值已經(jīng)存在"
};
// 驗(yàn)證給定的值長(zhǎng)度是否合理
validator.types.isLength={
validate: function (value) {
var l=value.toString().length
if ( l > 2 && l < 10) {
return true;
} else {
return false;
}
},
instructions: "長(zhǎng)度不合理,請(qǐng)長(zhǎng)度在2-10個(gè)字符內(nèi)"
};
復(fù)制代碼
上面對(duì)types規(guī)則進(jìn)行了補(bǔ)充,定義了幾種規(guī)則,至此,對(duì)于名稱校驗(yàn),簡(jiǎn)單的設(shè)定就敲完了。接下來(lái)要準(zhǔn)備的就是一個(gè)能夠在英雄聯(lián)盟合理的名字進(jìn)行驗(yàn)證:
var types=['isExist', 'isLength', 'isNoNumber', 'isNonEmpty']; // 決定想要的規(guī)則,無(wú)論增加或者減少,原函數(shù)都不需要改動(dòng)
function check (name, types) {
validator.validate(name, types);
if (validator.hasErrors()) {
console.log(validator.messages.join("\n"));
} else {
console.log('驗(yàn)證通過(guò)!')
}
}
check('okckokckokck', types) // 長(zhǎng)度不合理,請(qǐng)長(zhǎng)度在2-10個(gè)字符內(nèi)
check('老faker', types) // true
check('00001', types) // 傳入的值不能是純數(shù)字
復(fù)制代碼
首先設(shè)定好想要的規(guī)則,用一個(gè)types數(shù)組囊括進(jìn)來(lái),之后定義一個(gè)check函數(shù),把結(jié)果處理封裝一下,最后傳入?yún)?shù),無(wú)論想要檢測(cè)什么規(guī)則,都不需要修改原函數(shù)?,F(xiàn)在無(wú)論我想檢測(cè)faker可不可以注冊(cè),還是一個(gè)空字符串,都可以傳入規(guī)則,進(jìn)行使用。如果想添加新的規(guī)則,只需要在validator.types上續(xù)寫對(duì)象就可以,方便清晰,結(jié)構(gòu)明朗。
核心思想就是把復(fù)雜的算法結(jié)構(gòu),分別封裝起來(lái),讓他們之間可以互相替換,上面的代碼就很好的體現(xiàn)了 互相替換 ,因?yàn)闊o(wú)論我怎么去修改想要的規(guī)則,都不需要改動(dòng)原本的代碼。
在系統(tǒng)沿著多個(gè)維度變化的同時(shí),又不增加其復(fù)雜度并已達(dá)到解耦。將抽象部分與它的實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化。簡(jiǎn)單的說(shuō):橋接模式最主要的特點(diǎn)是實(shí)現(xiàn)層(如元素綁定的事件)與抽象層(如修飾頁(yè)面UI邏輯)解耦分離。
下面依然是一個(gè)例子:
假如我們還在英雄聯(lián)盟的世界里,每一場(chǎng)游戲最終都會(huì)有一個(gè)結(jié)局,無(wú)論勝利還是失敗,都會(huì)彈出一個(gè)窗口,告訴你 —— Victory或者是Defeat。
function GameMessage (type) { // 抽象 與 實(shí)現(xiàn) 的 橋梁
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')
}
}
// 實(shí)現(xiàn)層
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()
復(fù)制代碼
首先我們創(chuàng)建了一個(gè)GameMessage的函數(shù),我們都知道勝利失敗都有一半的概率,因此定義了switchVD變量,模擬一個(gè)隨機(jī)事件,同時(shí)每次結(jié)果調(diào)用一次getResult函數(shù),獲取最新結(jié)果。
橋接模式體現(xiàn)在GameMessage函數(shù)上,將抽象的 Victory() 以及 Defeat() 與 我們獲取結(jié)果的 getResult() 實(shí)現(xiàn)解耦。函數(shù)之間不糅合邏輯,但又通過(guò)橋梁函數(shù),連接在一起。
這么寫的好處就是,兩者都可以獨(dú)立的變化,互不打擾。畢竟如果揉在一起,可能邏輯如下:
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
復(fù)制代碼
上述代碼可以輕松的看到,如果沒(méi)有橋接模式,直接把實(shí)現(xiàn)層,渲染層糅合在一起,會(huì)依賴上下文。倘若獲取不到上下文的環(huán)境,很容易出現(xiàn)問(wèn)題。
橋接模式在日常開(kāi)發(fā)中,會(huì)在不經(jīng)意間頻繁使用,目的也是為了讓代碼結(jié)構(gòu)清晰,將不同邏輯的代碼互相解耦。便于日后維護(hù),開(kāi)發(fā)時(shí)也更能區(qū)分模塊,看的舒服,自然效率也高。
橋接模式關(guān)鍵是要理解抽象部分與實(shí)現(xiàn)部分的分離,使得二者可以獨(dú)立的變化,而不必拘泥于形式。靈活的變化,適用場(chǎng)景的多變就非常適合使用這種模式來(lái)實(shí)現(xiàn)。橋接模式最重要的是找到代碼中不同的變化緯度。
狀態(tài)模式(State)允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變的時(shí)候改變它的行為,對(duì)象看起來(lái)似乎修改了它的類。 其實(shí)就是用一個(gè)對(duì)象或者數(shù)組記錄一組狀態(tài),每個(gè)狀態(tài)對(duì)應(yīng)一個(gè)實(shí)現(xiàn),實(shí)現(xiàn)的時(shí)候根據(jù)狀態(tài)挨個(gè)去運(yùn)行實(shí)現(xiàn)。
優(yōu)點(diǎn):
缺點(diǎn):
比如下面我們定義一個(gè)英雄的狀態(tài),名字叫亞索,其中亞索可能同時(shí)有好幾個(gè)狀態(tài)比如 邊走邊攻擊 —— 我們俗稱的“走A”,還有可能釋放技能之后接一個(gè)“B鍵回家”的操作,當(dāng)然最有可能的是eqw閃r行云流水的操作收獲一個(gè)人頭,再接一個(gè)ctrl+f6等。
如果對(duì)這些操作一個(gè)個(gè)進(jìn)行處理判斷,需要多個(gè)if-else或switch不僅丑陋不說(shuō),而且在遇到有組合動(dòng)作的時(shí)候,實(shí)現(xiàn)就會(huì)更為冗余。那么我們這里的復(fù)雜操作,可以使用 狀態(tài)模式 來(lái)實(shí)現(xiàn)。
狀態(tài)模式 的思路是:首先創(chuàng)建一個(gè)狀態(tài)對(duì)象或者數(shù)組,在對(duì)象內(nèi)部存儲(chǔ)需要操作的狀態(tài)數(shù)組或?qū)ο螅缓鬆顟B(tài)對(duì)象提供一些接口,可以更改狀態(tài)以及執(zhí)行動(dòng)作。
那現(xiàn)在有一個(gè)英雄叫做亞索!下面代碼,我們就用亞索的狀態(tài)來(lái)實(shí)現(xiàn)一下傳說(shuō)中的狀態(tài)模式:
function YasuoState() {
//存儲(chǔ)當(dāng)前即將執(zhí)行動(dòng)作的狀態(tài)!
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() {
//清空當(dāng)前的動(dòng)作
this.currentstate=[];
Object.keys(arguments).forEach((i)=> this.currentstate.push(arguments[i]))
return this;
}
YasuoState.prototype.YasuoActions=function() {
//當(dāng)前動(dòng)作集合中的動(dòng)作依次執(zhí)行
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();
復(fù)制代碼
上面代碼成功實(shí)現(xiàn)了亞索的狀態(tài)模式,我們假設(shè)他有走路、攻擊、釋放技能、回家?guī)讉€(gè)狀態(tài),其中這幾個(gè)狀態(tài)其實(shí)是可以同時(shí)輸入指令的,要不然那些職業(yè)選手的高光操作就會(huì)在 技能銜接 而出現(xiàn)的卡頓 香消玉殞。
狀態(tài)模式最常見(jiàn)的就是日常的例子 —— 紅綠燈,每當(dāng)切換狀態(tài)的時(shí)候,執(zhí)行一次動(dòng)作。
至于英雄聯(lián)盟中,最常見(jiàn)的就是邊走邊攻擊,在輸入命令后,首先改變了我們對(duì)象的狀態(tài)yasuoState.changeState('magic','backhome'),然后因?yàn)樵诖a中有return this;,可以鏈?zhǔn)秸{(diào)用接下來(lái)的行為,于是我們讓它依次執(zhí)行剛才輸入的狀態(tài)。接下來(lái)又一次改變了狀態(tài)changeState('walk'),并且進(jìn)行執(zhí)行。可以看到執(zhí)行了兩次,由于狀態(tài)并沒(méi)有再次改變,因此只需要重復(fù)執(zhí)行就可以保證我們的英雄一直往前走下去了。
希望狀態(tài)模式可以幫助你解決絕大多數(shù),需要切換狀態(tài)的操作。遇到類似的問(wèn)題時(shí),可以迅速拿出成熟可靠的狀態(tài)模式解決之。
本次分享的三種模式,都可以在英雄聯(lián)盟中找到影子,因?yàn)槲蚁矚g這款游戲,所以很輕松可以找到其中使用的設(shè)計(jì)模式:
設(shè)計(jì)模式主要可以幫助我們解決,開(kāi)發(fā)中對(duì)代碼的設(shè)計(jì)問(wèn)題,那我們?nèi)绾握业胶线m的對(duì)象,并應(yīng)用合適的設(shè)計(jì)模式呢?
借用書中的幾個(gè)提示吧:
尋找合適的對(duì)象
決定對(duì)象的粒度
決定好這個(gè)對(duì)象設(shè)計(jì)的接口
把對(duì)象需要的具體函數(shù)實(shí)現(xiàn)
合理的運(yùn)用代碼復(fù)用機(jī)制
設(shè)計(jì)的代碼應(yīng)該可以支持變化,要對(duì)變化有預(yù)見(jiàn)性
大概是這幾種,在 javascript 中涉及編譯的場(chǎng)景較少,就不敘述了。
設(shè)計(jì)模式是軟件開(kāi)發(fā)人員在軟件開(kāi)發(fā)過(guò)程中面臨的一般問(wèn)題的解決方案。這些解決方案是眾多軟件開(kāi)發(fā)人員經(jīng)過(guò)相當(dāng)長(zhǎng)的一段時(shí)間的試驗(yàn)和錯(cuò)誤總結(jié)出來(lái)的。
根據(jù)上面的幾條規(guī)則,在開(kāi)發(fā)接口和函數(shù)的時(shí)候,時(shí)刻注意,就可以避免大多數(shù)代碼設(shè)計(jì)上的問(wèn)題,對(duì)于以后的維護(hù)也會(huì)有巨大的幫助。下一個(gè)接受代碼的人,也會(huì)十分感激你的,讀代碼其實(shí)和讀書一樣,你現(xiàn)在偷懶寫的代碼可能無(wú)所謂,后面接手的人會(huì)瘋狂吐槽。相反如果你優(yōu)雅的實(shí)現(xiàn),像我,就會(huì)心里由衷的佩服,看到整齊的函數(shù),注釋明朗的功能,不得不說(shuō),高手確實(shí)是高手啊,短短 200 行,讓人跪服,就突出一個(gè)詞 —— 優(yōu)雅。
作者:黃梵高
鏈接:https://juejin.im/post/5ec791fdf265da770d3daabd
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。