1.策略模式(Strategy)
策略模式定義了算法家族,分別封裝起來(lái),讓他們之間可以互相替換,此模式讓算法的變化不會(huì)影響到使用算法的客戶。
在理解策略模式之前,我們先來(lái)一個(gè)例子,一般情況下,如果我們要做數(shù)據(jù)合法性驗(yàn)證,很多時(shí)候都是按照swith語(yǔ)句來(lái)判斷,但是這就帶來(lái)幾個(gè)問(wèn)題,首先如果增加需求的話,我們還要再次修改這段代碼以增加邏輯,而且在進(jìn)行單元測(cè)試的時(shí)候也會(huì)越來(lái)越復(fù)雜,代碼如下:
// 所有可以的驗(yàn)證規(guī)則處理類存放的地方,后面會(huì)單獨(dú)定義
types: {},
// 驗(yàn)證類型所對(duì)應(yīng)的錯(cuò)誤消息
messages: [],
// 當(dāng)然需要使用的驗(yàn)證類型
config: {},
// 暴露的公開驗(yàn)證方法
// 傳入的參數(shù)是 key => value對(duì)
validate: function (data) {
var i, msg, type, checker, result_ok;
// 清空所有的錯(cuò)誤信息
this.messages = [];
for (i in data) {
if (data.hasOwnProperty(i)) {
type = this.config[i]; // 根據(jù)key查詢是否有存在的驗(yàn)證規(guī)則
checker = this.types[type]; // 獲取驗(yàn)證規(guī)則的驗(yàn)證類
if (!type) {
continue; // 如果驗(yàn)證規(guī)則不存在,則不處理
}
if (!checker) { // 如果驗(yàn)證規(guī)則類不存在,拋出異常
throw {
name: "ValidationError",
message: "No handler to validate type " + type
};
}
result_ok = checker.validate(data[i]); // 使用查到到的單個(gè)驗(yàn)證類進(jìn)行驗(yàn)證
if (!result_ok) {
msg = "Invalid value for *" + i + "*, " + checker.instructions;
this.messages.push(msg);
}
}
}
return this.hasErrors();
},
// helper
hasErrors: function () {
return this.messages.length !== 0;
}
};
// 驗(yàn)證給定的值是否是數(shù)字
validator.types.isNumber = {
validate: function (value) {
return !isNaN(value);
},
instructions: "傳入的值只能是合法的數(shù)字,例如:1, 3.14 or 2010"
};
// 驗(yàn)證給定的值是否只是字母或數(shù)字
validator.types.isAlphaNum = {
validate: function (value) {
return !/[^a-z0-9]/i.test(value);
},
instructions: "傳入的值只能保護(hù)字母和數(shù)字,不能包含特殊字符"
};
validator.config = {
first_name: 'isNonEmpty',
age: 'isNumber',
username: 'isAlphaNum'
};
if (validator.hasErrors()) {
console.log(validator.messages.join("\n"));
}
策略模式定義了一系列算法,從概念上來(lái)說(shuō),所有的這些算法都是做相同的事情,只是實(shí)現(xiàn)不同,他可以以相同的方式調(diào)用所有的方法,減少了各種算法類與使用算法類之間的耦合。
從另外一個(gè)層面上來(lái)說(shuō),單獨(dú)定義算法類,也方便了單元測(cè)試,因?yàn)榭梢酝ㄟ^(guò)自己的算法進(jìn)行單獨(dú)測(cè)試。
實(shí)踐中,不僅可以封裝算法,也可以用來(lái)封裝幾乎任何類型的規(guī)則,是要在分析過(guò)程中需要在不同時(shí)間應(yīng)用不同的業(yè)務(wù)規(guī)則,就可以考慮是要策略模式來(lái)處理各種變化。
2.裝飾者模式(Decorator)
裝飾者提供比繼承更有彈性的替代方案。 裝飾者用用于包裝同接口的對(duì)象,不僅允許你向方法添加行為,而且還可以將方法設(shè)置成原始對(duì)象調(diào)用(例如裝飾者的構(gòu)造函數(shù))。
裝飾者用于通過(guò)重載方法的形式添加新功能,該模式可以在被裝飾者前面或者后面加上自己的行為以達(dá)到特定的目的。
那么裝飾者模式有什么好處呢?前面說(shuō)了,裝飾者是一種實(shí)現(xiàn)繼承的替代方案。當(dāng)腳本運(yùn)行時(shí),在子類中增加行為會(huì)影響原有類所有的實(shí)例,而裝飾者卻不然。取而代之的是它能給不同對(duì)象各自添加新行為。如下代碼所示:
代碼如下:
//需要裝飾的類(函數(shù))
function Macbook() {
this.cost = function () {
return 1000;
};
}
function Memory(macbook) {
this.cost = function () {
return macbook.cost() + 75;
};
}
function BlurayDrive(macbook) {
this.cost = function () {
return macbook.cost() + 300;
};
}
function Insurance(macbook) {
this.cost = function () {
return macbook.cost() + 250;
};
}
// 用法
var myMacbook = new Insurance(new BlurayDrive(new Memory(new Macbook())));
console.log(myMacbook.cost());
下面是另一個(gè)實(shí)例,當(dāng)我們?cè)谘b飾者對(duì)象上調(diào)用performTask時(shí),它不僅具有一些裝飾者的行為,同時(shí)也調(diào)用了下層對(duì)象的performTask函數(shù)。
代碼如下:
function ConcreteClass() {
this.performTask = function () {
this.preTask();
console.log('doing something');
this.postTask();
};
}
function AbstractDecorator(decorated) {
this.performTask = function () {
decorated.performTask();
};
}
function ConcreteDecoratorClass(decorated) {
this.base = AbstractDecorator;
this.base(decorated);
decorated.preTask = function () {
console.log('pre-calling..');
};
decorated.postTask = function () {
console.log('post-calling..');
};
}
var concrete = new ConcreteClass();
var decorator1 = new ConcreteDecoratorClass(concrete);
var decorator2 = new ConcreteDecoratorClass(decorator1);
decorator2.performTask();
再來(lái)一個(gè)徹底的例子:
代碼如下:
var tree = {};
tree.decorate = function () {
console.log('Make sure the tree won\'t fall');
};
tree.getDecorator = function (deco) {
tree[deco].prototype = this;
return new tree[deco];
};
tree.RedBalls = function () {
this.decorate = function () {
this.RedBalls.prototype.decorate(); // 第7步:先執(zhí)行原型(這時(shí)候是Angel了)的decorate方法
console.log('Put on some red balls'); // 第8步 再輸出 red
// 將這2步作為RedBalls的decorate方法
}
};
tree.BlueBalls = function () {
this.decorate = function () {
this.BlueBalls.prototype.decorate(); // 第1步:先執(zhí)行原型的decorate方法,也就是tree.decorate()
console.log('Add blue balls'); // 第2步 再輸出blue
// 將這2步作為BlueBalls的decorate方法
}
};
tree.Angel = function () {
this.decorate = function () {
this.Angel.prototype.decorate(); // 第4步:先執(zhí)行原型(這時(shí)候是BlueBalls了)的decorate方法
console.log('An angel on the top'); // 第5步 再輸出angel
// 將這2步作為Angel的decorate方法
}
};
tree = tree.getDecorator('BlueBalls'); // 第3步:將BlueBalls對(duì)象賦給tree,這時(shí)候父原型里的getDecorator依然可用
tree = tree.getDecorator('Angel'); // 第6步:將Angel對(duì)象賦給tree,這時(shí)候父原型的父原型里的getDecorator依然可用
tree = tree.getDecorator('RedBalls'); // 第9步:將RedBalls對(duì)象賦給tree
tree.decorate(); // 第10步:執(zhí)行RedBalls對(duì)象的decorate方法
總結(jié)
裝飾者模式是為已有功能動(dòng)態(tài)地添加更多功能的一種方式,把每個(gè)要裝飾的功能放在單獨(dú)的函數(shù)里,然后用該函數(shù)包裝所要裝飾的已有函數(shù)對(duì)象,因此,當(dāng)需要執(zhí)行特殊行為的時(shí)候,調(diào)用代碼就可以根據(jù)需要有選擇地、按順序地使用裝飾功能來(lái)包裝對(duì)象。優(yōu)點(diǎn)是把類(函數(shù))的核心職責(zé)和裝飾功能區(qū)分開了。
3.代理模式(Proxy)
代理,顧名思義就是幫助別人做事,GoF對(duì)代理模式的定義如下:
代理模式(Proxy),為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。
代理模式使得代理對(duì)象控制具體對(duì)象的引用。代理幾乎可以是任何對(duì)象:文件,資源,內(nèi)存中的對(duì)象,或者是一些難以復(fù)制的東西。
我們來(lái)舉一個(gè)簡(jiǎn)單的例子,假如dudu要送酸奶小妹玫瑰花,卻不知道她的聯(lián)系方式或者不好意思,想委托大叔去送這些玫瑰,那大叔就是個(gè)代理(其實(shí)挺好的,可以扣幾朵給媳婦),那我們?nèi)绾蝸?lái)做呢?
// 這是dudu
var dudu = function (girl) {
this.girl = girl;
this.sendGift = function (gift) {
alert("Hi " + girl.name + ", dudu送你一個(gè)禮物:" + gift);
}
};
// 大叔是代理
var proxyTom = function (girl) {
this.girl = girl;
this.sendGift = function (gift) {
(new dudu(girl)).sendGift(gift); // 替dudu送花咯
}
};
調(diào)用方式就非常簡(jiǎn)單了:
實(shí)戰(zhàn)一把
通過(guò)上面的代碼,相信大家對(duì)代理模式已經(jīng)非常清楚了,我們來(lái)實(shí)戰(zhàn)下:我們有一個(gè)簡(jiǎn)單的播放列表,需要在點(diǎn)擊單個(gè)連接(或者全選)的時(shí)候在該連接下方顯示視頻曲介紹以及play按鈕,點(diǎn)擊play按鈕的時(shí)候播放視頻,列表結(jié)構(gòu)如下:
我們先來(lái)分析如下,首先我們不僅要監(jiān)控a連接的點(diǎn)擊事件,還要監(jiān)控“全選/反選”的點(diǎn)擊事件,然后請(qǐng)求服務(wù)器查詢視頻信息,組裝HTML信息顯示在li元素的最后位置上,效果如下:
然后再監(jiān)控play連接的點(diǎn)擊事件,點(diǎn)擊以后開始播放,效果如下:
好了,開始,沒(méi)有jQuery,我們自定義一個(gè)選擇器:
sql = sql.replace('%ID%', ids.join('","'));
sql = encodeURIComponent(sql);
url += sql + '&' + format + '&' + handler;
script.src = url;
document.body.appendChild(script);
}
};
代理對(duì)象如下:
// 添加到隊(duì)列dd to the queue
this.ids.push(id);
this.callback = callback;
this.context = context;
// 設(shè)置timeout
if (!this.timeout) {
this.timeout = setTimeout(function () {
proxy.flush();
}, this.delay);
}
},
// 觸發(fā)請(qǐng)求,使用代理職責(zé)調(diào)用了http.makeRequest
flush: function () {
// proxy.handler為請(qǐng)求yahoo時(shí)的callback
http.makeRequest(this.ids, 'proxy.handler');
// 請(qǐng)求數(shù)據(jù)以后,緊接著執(zhí)行proxy.handler方法(里面有另一個(gè)設(shè)置的callback)
// 清楚timeout和隊(duì)列
this.timeout = null;
this.ids = [];
},
handler: function (data) {
var i, max;
// 單個(gè)視頻的callback調(diào)用
if (parseInt(data.query.count, 10) === 1) {
proxy.callback.call(proxy.context, data.query.results.Video);
return;
}
// 多個(gè)視頻的callback調(diào)用
for (i = 0, max = data.query.results.Video.length; i < max; i += 1) {
proxy.callback.call(proxy.context, data.query.results.Video[i]);
}
}
};
視頻處理模塊主要有3種子功能:獲取信息、展示信息、播放視頻:
if (data.query) {
data = data.query.results.Video;
}
id = data.id;
html += '<img src="' + data.Image[0].url + '" width="50" \/>';
html += '<h2>' + data.title + '<\/h2>';
html += '<p>' + data.copyrightYear + ', ' + data.label + '<\/p>';
if (data.Album) {
html += '<p>Album: ' + data.Album.Release.title + ', ' + data.Album.Release.releaseYear + '<br \/>';
}
html += '<p><a class="play" href="http://new.music.yahoo.com/videos/--' + id + '">? play<\/a><\/p>';
info = document.createElement('div');
info.id = "info" + id;
info.innerHTML = html;
$('v' + id).appendChild(info);
},
// 獲取信息并顯示
getInfo: function (id) {
var info = $('info' + id);
if (!info) {
proxy.makeRequest(id, videos.updateList, videos); //執(zhí)行代理職責(zé),并傳入videos.updateList回調(diào)函數(shù)
return;
}
if (info.style.display === "none") {
info.style.display = '';
} else {
info.style.display = 'none';
}
}
};
現(xiàn)在可以處理點(diǎn)擊事件的代碼了,由于有很多a連接,如果每個(gè)連接都綁定事件的話,顯然性能會(huì)有問(wèn)題,所以我們將事件綁定在<ol>元素上,然后檢測(cè)點(diǎn)擊的是否是a連接,如果是說(shuō)明我們點(diǎn)擊的是視頻地址,然后就可以播放了:
e = e || window.event;
src = e.target || e.srcElement;
// 不是連接的話就不繼續(xù)處理了
if (src.nodeName.toUpperCase() !== "A") {
return;
}
//停止冒泡
if (typeof e.preventDefault === "function") {
e.preventDefault();
}
e.returnValue = false;
id = src.href.split('--')[1];
//如果點(diǎn)擊的是已經(jīng)生產(chǎn)的視頻信息區(qū)域的連接play,就開始播放
// 然后return不繼續(xù)了
if (src.className === "play") {
src.parentNode.innerHTML = videos.getPlayer(id);
return;
}
src.parentNode.id = "v" + id;
videos.getInfo(id); // 這個(gè)才是第一次點(diǎn)擊的時(shí)候顯示視頻信息的處理代碼
};
全選反選的代碼大同小異,我們就不解釋了:
var hrefs, i, max, id;
hrefs = $('vids').getElementsByTagName('a');
for (i = 0, max = hrefs.length; i < max; i += 1) {
// 忽略play連接
if (hrefs[i].className === "play") {
continue;
}
// 忽略沒(méi)有選擇的項(xiàng)
if (!hrefs[i].parentNode.firstChild.checked) {
continue;
}
id = hrefs[i].href.split('--')[1];
hrefs[i].parentNode.id = "v" + id;
videos.getInfo(id);
}
};
總結(jié)
代理模式一般適用于如下場(chǎng)合:
1.遠(yuǎn)程代理,也就是為了一個(gè)對(duì)象在不同的地址空間提供局部代表,這樣可以隱藏一個(gè)對(duì)象存在于不同地址空間的事實(shí),就像web service里的代理類一樣。
2.虛擬代理,根據(jù)需要?jiǎng)?chuàng)建開銷很大的對(duì)象,通過(guò)它來(lái)存放實(shí)例化需要很長(zhǎng)時(shí)間的真實(shí)對(duì)象,比如瀏覽器的渲染的時(shí)候先顯示問(wèn)題,而圖片可以慢慢顯示(就是通過(guò)虛擬代理代替了真實(shí)的圖片,此時(shí)虛擬代理保存了真實(shí)圖片的路徑和尺寸。
3.安全代理,用來(lái)控制真實(shí)對(duì)象訪問(wèn)時(shí)的權(quán)限,一般用于對(duì)象應(yīng)該有不同的訪問(wèn)權(quán)限。
4.智能指引,只當(dāng)調(diào)用真實(shí)的對(duì)象時(shí),代理處理另外一些事情。例如C#里的垃圾回收,使用對(duì)象的時(shí)候會(huì)有引用次數(shù),如果對(duì)象沒(méi)有引用了,GC就可以回收它了。
4.工廠模式(Factory)
工廠模式也是對(duì)象創(chuàng)建模式之一,它通常在類或類的靜態(tài)方法中去實(shí)現(xiàn)。構(gòu)造對(duì)象的一種方式是使用new操作符,但使用new時(shí)正是針對(duì)實(shí)現(xiàn)編程,會(huì)造成“耦合”問(wèn)題,與具體的類關(guān)系緊密。導(dǎo)致代碼更脆弱,缺乏彈性,在復(fù)雜邏輯的項(xiàng)目中建議是面向接口編程。
先看簡(jiǎn)單工廠模式
5.模板模式(Template)
一、定義
模板方法是基于繼承的設(shè)計(jì)模式,可以很好的提高系統(tǒng)的擴(kuò)展性。 java中的抽象父類、子類
模板方法有兩部分結(jié)構(gòu)組成,第一部分是抽象父類,第二部分是具體的實(shí)現(xiàn)子類。
二、示例
Coffee or Tea
(1) 把水煮沸
(2) 用沸水浸泡茶葉
(3) 把茶水倒進(jìn)杯子
(4) 加檸檬
/* 抽象父類:飲料 */
var Beverage = function(){};
// (1) 把水煮沸
Beverage.prototype.boilWater = function() {
console.log("把水煮沸");
};
// (2) 沸水浸泡
Beverage.prototype.brew = function() {
throw new Error("子類必須重寫brew方法");
};
// (3) 倒進(jìn)杯子
Beverage.prototype.pourInCup = function() {
throw new Error("子類必須重寫pourInCup方法");
};
// (4) 加調(diào)料
Beverage.prototype.addCondiments = function() {
throw new Error("子類必須重寫addCondiments方法");
};
/* 模板方法 */
Beverage.prototype.init = function() {
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
}
/* 實(shí)現(xiàn)子類 Coffee*/
var Coffee = function(){};
Coffee.prototype = new Beverage();
// 重寫非公有方法
Coffee.prototype.brew = function() {
console.log("用沸水沖泡咖啡");
};
Coffee.prototype.pourInCup = function() {
console.log("把咖啡倒進(jìn)杯子");
};
Coffee.prototype.addCondiments = function() {
console.log("加牛奶");
};
var coffee = new Coffee();
coffee.init();
通過(guò)模板方法模式,在父類中封裝了子類的算法框架。這些算法框架在正常狀態(tài)下是適用大多數(shù)子類的,但也會(huì)出現(xiàn)“個(gè)性”子類。
如上述流程,加調(diào)料是可選的。
鉤子方法可以解決這個(gè)問(wèn)題,放置鉤子是隔離變化的一種常見(jiàn)手段。
/* 添加鉤子方法 */
Beverage.prototype.customerWantsCondiments = function() {
return true;
};
Beverage.prototype.init = function() {
this.boilWater();
this.brew();
this.pourInCup();
if(this.customerWantsCondiments()) {
this.addCondiments();
}
}
/* 實(shí)現(xiàn)子類 Tea*/
var Tea = function(){};
Tea.prototype = new Beverage();
// 重寫非公有方法
Tea.prototype.brew = function() {
console.log("用沸水沖泡茶");
};
Tea.prototype.pourInCup = function() {
console.log("把茶倒進(jìn)杯子");
};
Tea.prototype.addCondiments = function() {
console.log("加牛奶");
};
Tea.prototype.customerWantsCondiments = function() {
return window.confirm("需要添加調(diào)料嗎?");
};
var tea = new Tea();
tea.init();
JavaScript沒(méi)有提供真正的類式繼承,繼承是通過(guò)對(duì)象與對(duì)象之間的委托來(lái)實(shí)現(xiàn)的。
三、“好萊塢原則”:別調(diào)用我們,我們會(huì)調(diào)用你
典型使用場(chǎng)景:
(1)模板方法模式:使用該設(shè)計(jì)模式意味著子類放棄了對(duì)自己的控制權(quán),而是改為父類通知子類。作為子類,只負(fù)責(zé)提供一些設(shè)計(jì)上的細(xì)節(jié)。
(2)觀察者模式:發(fā)布者把消息推送給訂閱者。
(3)回調(diào)函數(shù):ajax異步請(qǐng)求,把需要執(zhí)行的操作封裝在回調(diào)函數(shù)里,當(dāng)數(shù)據(jù)返回后,這個(gè)回調(diào)函數(shù)才被執(zhí)行。
6.外觀模式(Facade)
外觀模式(Facade)為子系統(tǒng)中的一組接口提供了一個(gè)一致的界面,此模塊定義了一個(gè)高層接口,這個(gè)接口值得這一子系統(tǒng)更加容易使用。
外觀模式不僅簡(jiǎn)化類中的接口,而且對(duì)接口與調(diào)用者也進(jìn)行了解耦。外觀模式經(jīng)常被認(rèn)為開發(fā)者必備,它可以將一些復(fù)雜操作封裝起來(lái),并創(chuàng)建一個(gè)簡(jiǎn)單的接口用于調(diào)用。
外觀模式經(jīng)常被用于JavaScript類庫(kù)里,通過(guò)它封裝一些接口用于兼容多瀏覽器,外觀模式可以讓我們間接調(diào)用子系統(tǒng),從而避免因直接訪問(wèn)子系統(tǒng)而產(chǎn)生不必要的錯(cuò)誤。
外觀模式的優(yōu)勢(shì)是易于使用,而且本身也比較輕量級(jí)。但也有缺點(diǎn) 外觀模式被開發(fā)者連續(xù)使用時(shí)會(huì)產(chǎn)生一定的性能問(wèn)題,因?yàn)樵诿看握{(diào)用時(shí)都要檢測(cè)功能的可用性。
下面是一段未優(yōu)化過(guò)的代碼,我們使用了外觀模式通過(guò)檢測(cè)瀏覽器特性的方式來(lái)創(chuàng)建一個(gè)跨瀏覽器的使用方法。
總結(jié)
那么何時(shí)使用外觀模式呢?一般來(lái)說(shuō)分三個(gè)階段:
首先,在設(shè)計(jì)初期,應(yīng)該要有意識(shí)地將不同的兩個(gè)層分離,比如經(jīng)典的三層結(jié)構(gòu),在數(shù)據(jù)訪問(wèn)層和業(yè)務(wù)邏輯層、業(yè)務(wù)邏輯層和表示層之間建立外觀Facade。
其次,在開發(fā)階段,子系統(tǒng)往往因?yàn)椴粩嗟闹貥?gòu)演化而變得越來(lái)越復(fù)雜,增加外觀Facade可以提供一個(gè)簡(jiǎn)單的接口,減少他們之間的依賴。
第三,在維護(hù)一個(gè)遺留的大型系統(tǒng)時(shí),可能這個(gè)系統(tǒng)已經(jīng)很難維護(hù)了,這時(shí)候使用外觀Facade也是非常合適的,為系系統(tǒng)開發(fā)一個(gè)外觀Facade類,為設(shè)計(jì)粗糙和高度復(fù)雜的遺留代碼提供比較清晰的接口,讓新系統(tǒng)和Facade對(duì)象交互,F(xiàn)acade與遺留代碼交互所有的復(fù)雜工作。
在軟件系統(tǒng)中,有時(shí)候面臨著“一個(gè)復(fù)雜對(duì)象”的創(chuàng)建工作,其通常由各個(gè)部分的子對(duì)象用一定的算法構(gòu)成;由于需求的變化,這個(gè)復(fù)雜對(duì)象的各個(gè)部分經(jīng)常面臨著劇烈的變化,但是將它們組合在一起的算法確相對(duì)穩(wěn)定。如何應(yīng)對(duì)這種變化?如何提供一種“封裝機(jī)制”來(lái)隔離出“復(fù)雜對(duì)象的各個(gè)部分”的變化,從而保持系統(tǒng)中的“穩(wěn)定構(gòu)建算法”不隨著需求改變而改變?這就是要說(shuō)的建造者模式。
建造者模式可以將一個(gè)復(fù)雜對(duì)象的構(gòu)建與其表示相分離,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表示。也就是說(shuō)如果我們用了建造者模式,那么用戶就需要指定需要建造的類型就可以得到它們,而具體建造的過(guò)程和細(xì)節(jié)就不需要知道了。
這個(gè)模式相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,先上代碼,然后再解釋
var el = document.querySelector('#test');
el.addEventListener('click', getBeerByIdBridge, false);
function getBeerByIdBridge(e) {
getBeerById(this.id, function (beer) {
console.log('Requested Beer: ' + beer);
});
}
根據(jù)建造者的定義,表相即是回調(diào),也就是說(shuō)獲取數(shù)據(jù)以后如何顯示和處理取決于回調(diào)函數(shù),相應(yīng)地回調(diào)函數(shù)在處理數(shù)據(jù)的時(shí)候不需要關(guān)注是如何獲取數(shù)據(jù)的,同樣的例子也可以在jquery的ajax方法里看到,有很多回調(diào)函數(shù)(比如success, error回調(diào)等),主要目的就是職責(zé)分離。
同樣再來(lái)一個(gè)jQuery的例子:
總結(jié)
建造者模式主要用于“分步驟構(gòu)建一個(gè)復(fù)雜的對(duì)象”,在這其中“分步驟”是一個(gè)穩(wěn)定的算法,而復(fù)雜對(duì)象的各個(gè)部分則經(jīng)常變化,其優(yōu)點(diǎn)是:建造者模式的“加工工藝”是暴露的,這樣使得建造者模式更加靈活,并且建造者模式解耦了組裝過(guò)程和創(chuàng)建具體部件,使得我們不用去關(guān)心每個(gè)部件是如何組裝的。
8.觀察者模式(Observer)
觀察者模式有時(shí)也稱為發(fā)布--訂閱模式,在觀察者模式中,有一個(gè)觀察者可以管理所有的目標(biāo),等到有狀態(tài)發(fā)生改變的時(shí)候發(fā)出通知。(其實(shí)sql server中的發(fā)布訂閱也是這個(gè)道理)
假如以前村里的廣播是一個(gè)觀察者,那么每個(gè)村民就是被觀察對(duì)象,如果村子里有通知,政策發(fā)生改變的時(shí)候,就需要通過(guò)廣播把這個(gè)消息發(fā)布出去,而不用直接一家家的跑去發(fā)通知。
代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>觀察者模式</title>
</head>
<body>
<script>
var observer = {//觀察者
villagers: [],//村名
addVillager: function (callback) {//增加村名
this.villagers[this.villagers.length] = callback;
},
removeVillager: function (callback) {//移除村名
for (var i = 0; i < this.villagers.length; i++) {
if (this.villagers[i] === callback) {
delete this.villagers[i];
}
}
},
publish: function (info) {//發(fā)布信息
for (var i = 0; i < this.villagers.length; i++) {
if (typeof this.villagers[i] === 'function') {
this.villagers[i](info);
}
}
},
make: function (o) {//這里將村子建一個(gè)這種廣播方式
for (var i in this) {
o[i] = this[i];
}
}
};
var village1 = {};
observer.make(village1);//將村子1建立這種觀察者模式
var villager11 = {
read: function (what) {
console.log('我是第一個(gè)村子的第一個(gè)村名:' + what);
}
};
var villager12 = {
read: function (what) {
console.log('我是第一個(gè)村子的第二個(gè)村名:'+what);
}
};
village1.addVillager(villager11.read);
village1.addVillager(villager12.read);
village1.publish('大家來(lái)開會(huì)呀?。?!');
village1.removeVillager(villager11.read);
village1.publish('大家來(lái)開會(huì)呀!?。?#39;);
/* var village2 = {
myAddVillager:function(callback){
this.addVillager(callback);
},
myRemoveVillager:function(callback){
this.removeVillager(callback);
},
myPublish:function(info){
this.publish(info);
}
};
observer.make(village2);//將村子1建立這種觀察者模式
var villager21 = {
read: function (what) {
console.log('我是第二個(gè)村子的第一個(gè)村名:' + what);
}
};
var villager22 = {
read: function (what) {
console.log('我是第二個(gè)村子的第二個(gè)村名:'+what);
}
};
village2.myAddVillager(villager21.read);
village2.myAddVillager(villager22.read);
village2.myPublish('大家來(lái)領(lǐng)豬肉了?。?!');*/
</script>
</body>
</html>
寫到這里觀察者模式實(shí)現(xiàn)了,但是可能會(huì)有多個(gè)村子需要這種模式,那我們這里將observer改造成構(gòu)造函數(shù)的方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>觀察者模式</title>
</head>
<body>
<script>
function Observer(){//觀察者,這里采用構(gòu)造函數(shù),可以對(duì)不同村落進(jìn)行使用
if(!(this instanceof Observer)){
return new Observer();
}
this.villagers = [];
};
Observer.prototype = {
// villagers: [],//村名
addVillager: function (callback) {//增加村名
this.villagers[this.villagers.length] = callback;
},
removeVillager: function (callback) {//移除村名
for (var i = 0; i < this.villagers.length; i++) {
if (this.villagers[i] === callback) {
delete this.villagers[i];
}
}
},
publish: function (info) {//發(fā)布信息
for (var i = 0; i < this.villagers.length; i++) {
if (typeof this.villagers[i] === 'function') {
this.villagers[i](info);
}
}
},
make: function (o) {//這里將村子建一個(gè)這種廣播方式
for (var i in this) {
o[i] = this[i];
}
}
}
var village1 = {};
var observer1 = new Observer();
observer1.make(village1);//將村子1建立這種觀察者模式
var villager11 = {
read: function (what) {
console.log('我是第一個(gè)村子的第一個(gè)村名:' + what);
}
};
var villager12 = {
read: function (what) {
console.log('我是第一個(gè)村子的第二個(gè)村名:'+what);
}
};
village1.addVillager(villager11.read);
village1.addVillager(villager12.read);
village1.publish('大家來(lái)開會(huì)呀?。?!');
village1.removeVillager(villager11.read);
village1.publish('大家來(lái)開會(huì)呀!??!');
var village2 = {
myAddVillager:function(callback){
this.addVillager(callback);
},
myRemoveVillager:function(callback){
this.removeVillager(callback);
},
myPublish:function(info){
this.publish(info);
}
};
var observer2 = new Observer();
observer2.make(village2);//將村子1建立這種觀察者模式
var villager21 = {
read: function (what) {
console.log('我是第二個(gè)村子的第一個(gè)村名:' + what);
}
};
var villager22 = {
read: function (what) {
console.log('我是第二個(gè)村子的第二個(gè)村名:'+what);
}
};
village2.myAddVillager(villager21.read);
village2.myAddVillager(villager22.read);
village2.myPublish('大家來(lái)領(lǐng)豬肉了?。?!');
</script>
</body>
</html>
9.抽象工廠模式(Abstract Factory)
抽象工廠模式說(shuō)明
1. 工廠方法模式的問(wèn)題: 在工廠方法模式里,創(chuàng)建類都需要通過(guò) 工廠類,如果要擴(kuò)展程序,就必須修改工廠類,這違背了閉包原則,對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉;對(duì)于設(shè)計(jì)有一定的問(wèn)題。
2. 如何解決:就要用到抽象工廠模式,就是對(duì)功能類單獨(dú)創(chuàng)建工廠類,這樣就不必修改之前的代碼,又?jǐn)U展了功能。
3. 工廠模式其實(shí)就是對(duì) 實(shí)現(xiàn)同一接口的 實(shí)現(xiàn)類 的 統(tǒng)一 工廠方式創(chuàng)建調(diào)用,但 javascript 沒(méi)有接口這號(hào)東西,所以就去掉這一層 實(shí)現(xiàn),但位功能類的成員及方法都應(yīng)當(dāng)一樣;
抽象工廠源碼例子
1. 郵件發(fā)送類:
MailSender.prototype.send = function() {
//send body
}
2. 短信發(fā)送類:
SmsSender.prototype.send = function() {
//send body
}
3. 這里本來(lái)是創(chuàng)建工廠接口類,這里就去掉了; 直接創(chuàng)建各功能類工廠;
1>. 郵件工廠類:
2>. 短信工廠類:
4. 使用方法:
其他說(shuō)明
在面向?qū)ο笳Z(yǔ)言如 java,.net C# 使用的工廠模式,都用到接口,接口是對(duì)外向各種用戶暴露的可用方法,說(shuō)明這個(gè)功能應(yīng)用有些什么的方法應(yīng)用,用戶應(yīng)該怎么用這個(gè)接口。對(duì)象以類的形式表現(xiàn)出來(lái),代表現(xiàn)實(shí)世界中的某種抽象,也許場(chǎng)景會(huì)有很多類似的應(yīng)用,比如上面的 郵件發(fā)送,短信發(fā)送,再比如商場(chǎng)中的各種促銷手段,以及動(dòng)物世界中的各種飛禽走獸等..
如果我們不以接口形式提供用戶使用,勢(shì)必提供暴露真實(shí)的功能類對(duì)象給用戶,用戶可以隨意對(duì)類對(duì)象進(jìn)行修改跟擴(kuò)展,這是不允許的。
工廠方法模式 跟 抽象工廠模式可以很好的解決這樣的問(wèn)題,用戶只能使用接口調(diào)用工廠類,來(lái)進(jìn)行規(guī)定的操作;抽象工廠模式更進(jìn)一步使用擴(kuò)展功能變得容易,功能類跟工廠類都在實(shí)現(xiàn)相應(yīng)的接口上實(shí)現(xiàn)各自類級(jí)別的擴(kuò)展,不會(huì)涉及修改到其他的類或方法;
10.適配器模式(Adapter)
說(shuō)明: 適配器模式,一般是為要使用的接口,不符本應(yīng)用或本系統(tǒng)使用,而需引入的中間適配層類或?qū)ο蟮那闆r;
場(chǎng)景: 就好比我們買了臺(tái)手機(jī),買回來(lái)后發(fā)現(xiàn),充電線插頭是三插頭,但家里,只有兩插頭的口的插座,怎么辦?為了方便,也有為能在任何地方都能充上電,就得去買個(gè)通用充電適配器; 這樣手機(jī)才能在自己家里充上電;不然只能放著,或跑到有這個(gè)插頭的地方充電;
實(shí)際開發(fā)環(huán)境下,由于舊的系統(tǒng),或第三方應(yīng)用提供的接口,與我們定義的接口不匹配,在以面向接口編程的環(huán)境下,就無(wú)法使用這樣舊的,或第三方的接口,這時(shí)我們就使用適配類繼承待適匹配的類,并讓適配類實(shí)現(xiàn)接口的方式來(lái)引入舊的系統(tǒng)或第三方應(yīng)用的接口;
這樣使用接口編程時(shí),就可以使用這個(gè)適匹配類,來(lái)間接調(diào)用舊的系統(tǒng)或第三方應(yīng)用的接口。
在 Javascript 要實(shí)現(xiàn)類似動(dòng)態(tài)面向?qū)ο笳Z(yǔ)言的適配器模式的代碼,可以使用到 prototype 的繼承實(shí)例來(lái)實(shí)現(xiàn);因?yàn)槭腔诮涌诩s束的,但是Javascript沒(méi)有接口這號(hào)東西,我們?nèi)サ艚涌谶@一層,直接實(shí)現(xiàn)接口實(shí)現(xiàn)類 Target ,模擬類似的源碼出來(lái);
源碼實(shí)例
1. 待適配的類及接口方法:
代碼如下:
2. 普通實(shí)現(xiàn)類 [由于 Javascript 中沒(méi)有接口,所以就直接提供實(shí)現(xiàn)類]
Target.prototype.queryName= function() {
return this.name;
}
3. 適配類:
Adapte.prototype = new Adaptee();
Adapte.prototype.queryName = function() {
this.getName();
}
4.使用方法:
var adapte = new Adapte();
adapte.queryName(); //調(diào)用舊的系統(tǒng)或第三方應(yīng)用接口;
其他說(shuō)明
上面第四步,var local 以及 var adapte 類似像 Java,C# 這樣的面向?qū)ο笳Z(yǔ)言中接口引用指定,如:
//適配器
Target adapte = new Adapte();
adapte.queryName();
可見(jiàn)適配器類是連接接口與目標(biāo)類接口的中間層;就是用來(lái)解決,需要的目標(biāo)已經(jīng)存在了,但我們無(wú)法直接使用,不能跟我們的代碼定義協(xié)同使用,就得使用適器模式,適配器模式也叫轉(zhuǎn)換模式,包裝模式;
11.單例模式(Singleton)
單例模式的定義:保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)。
單例模式是一種常用的模式,有一些對(duì)象我們往往只需要一個(gè),比如線程池、全局緩存、瀏覽器的window對(duì)象。在js開發(fā)中,單例模式的用途同樣非常廣泛。試想一下,當(dāng)我們單擊登錄按鈕的時(shí)候,頁(yè)面中會(huì)出現(xiàn)一個(gè)登錄框,而這個(gè)浮窗是唯一的,無(wú)論單擊多少次登錄按鈕,這個(gè)浮窗只會(huì)被創(chuàng)建一次。因此這個(gè)登錄浮窗就適合用單例模式。
1、單例模式的使用場(chǎng)景
在使用一種模式之前,我們最好要知道,這種模式的使用場(chǎng)景。用了這么久的單例模式,竟全然不知!用它具體有哪些好處呢?
1).可以用它來(lái)劃分命名空間(這個(gè)就是就是經(jīng)常用的了)
2).利用分支技術(shù)來(lái)封裝瀏覽器之間的差異(這個(gè)還真沒(méi)用過(guò),挺新鮮)
3).借助單例模式,可以把代碼組織的更為一致,方便閱讀與維護(hù)(這個(gè)也用過(guò)了)
2、最基本的單例模式
最簡(jiǎn)單的單例其實(shí)就是一個(gè)對(duì)象字面量。它把一批有一定關(guān)聯(lián)的方法和屬性組織在一起。
var Singleton = {
attr1: true ,
attr2: 10 ,
method1 : function(){
alert('我是方法1');
},
method2 : function(){
alert('我是方法2');
}
};
這個(gè)對(duì)象可以被修改。你可以添加屬性和方法。你也可以用delete運(yùn)算符刪除現(xiàn)有成員。這實(shí)際上違背了面向?qū)ο笤O(shè)計(jì)的一條原則:類可以被擴(kuò)展,但不應(yīng)該被修改。如果某些變量需要保護(hù),那么可以將其定義在閉包中。
對(duì)象字面量只是創(chuàng)建單例的方法之一。也并非所有的對(duì)象字面量都是單例,那些只是用來(lái)模仿關(guān)聯(lián)數(shù)組或容納數(shù)據(jù)的對(duì)象字面量顯然不是單例。
3、借用閉包創(chuàng)建單例
閉包主要的目地 保護(hù)數(shù)據(jù)
// 命名空間
var BHX = {} ;
BHX.Singleton = (function(){
// 添加自己的私有成員
var a1 = true ;
var a2 = 10 ;
var f1 = function(){
alert('f1');
}
var f2 = function(){
alert('f2');
}
// 把塊級(jí)作用域里的執(zhí)行結(jié)果賦值給我的單例對(duì)象
return {
attr1: a1 ,
attr2: a2 ,
method1 : function(){
return f1();
},
method2 : function(){
return f2();
}
} ;
})();
alert(BHX.Singleton.attr1);
BHX.Singleton.method1();
這種單例模式又稱模塊模式,指的是它可以把一批相關(guān)的方法和屬性組織為模塊并起到劃分命名空間的作用。
4、單例模式用于劃分命名空間
1)、防止全局聲明的修改
/*using a namespace*/
var BHX = {};
BHX.Singleton = {
attr1: true ,
attr2: 10 ,
method1 : function(){
alert('我是方法1');
},
method2 : function(){
alert('我是方法2');
}
};
BHX.Singleton.attr1;
var attr1 = false;
這樣以來(lái),即使我們?cè)谕饷媛暶髁讼嗤淖兞?,也能在一定程度上防止attr1的被修改。
2)、防止其它來(lái)源代碼的修改
現(xiàn)在網(wǎng)頁(yè)上的JavaScript代碼往往不止用一個(gè)來(lái)源,什么庫(kù)代碼、廣告代碼和徽章代碼。為了避免與自己代碼的沖突,可以定義一個(gè)包含自己所有代碼的對(duì)象。
var XGP = {};
XGP.Common = {
//A singleton with common methods used by all objects and modules
}
XGP.ErrorCodes = {
//An object literal used to store data
}
XGP.PageHandler = {
//A singleton with page specific methods and attributes.
}
3)、用作專用代碼封裝
在擁有許多網(wǎng)頁(yè)的網(wǎng)站中,有些代碼是所有網(wǎng)頁(yè)都要用到的,他們通常被存放在獨(dú)立的文件中;而有些代碼則是某個(gè)網(wǎng)頁(yè)專用的,不會(huì)被用到其他地方。最好把這兩種代碼分別包裝在自己的單例對(duì)象中。
我們經(jīng)常要用Javascript為表單添加功能。出于平穩(wěn)退化方面的考慮,通常先創(chuàng)建一個(gè)不依賴于Javascript的、使用普通提交機(jī)制完成任務(wù)的純HTML網(wǎng)頁(yè)。
XGP.RegPage = {
FORM_ID: 'reg-form',
OUTPUT_ID: 'reg-result',
handleSubmit: function(e){
e.preventDefault(); //stop the normal form submission
var data = {};
var inputs = XGP.RegPage.formEl.getElementByTagName('input');
for(var i=0, len=inputs.length; i<len; i++){
data[inputs[i].name] = inputs[i].value;
}
XGP.RegPage.sendRegistration(data);
},
sendRegistration: function(data){
//make an xhr request and call displayResult() when response is recieved
...
},
displayResult: function(response){
XGP.RegPage.outputEl.innerHTML = response;
},
init: function(){
XGP.RegPage.formEl =$(XGP.RegPage.Form_ID);
XGP.RegPage.outputEl = $(XGP.RegPage.OUTPUT_ID);
//hijack the form submission
addEvent(XGP.RegPage.formEl, 'submit', XGP.RegPage.handleSubmit);
}
}
//invoke initialization method after the page load
addLoadEvent(XGP.RegPage.init);
5、惰性單例
前面所講的單例模式又一個(gè)共同點(diǎn):?jiǎn)卫龑?duì)象都是在腳本加載時(shí)被創(chuàng)建出來(lái)。對(duì)于資源密集的或配置開銷甚大的單例,更合理的做法是將其實(shí)例化推遲到需要使用他的時(shí)候。
這種技術(shù)就是惰性加載(lazy loading)。
實(shí)現(xiàn)步驟如下:
1).將所有代碼移到constructor方法中
2).全權(quán)控制調(diào)用時(shí)機(jī)(正是getInstance所要做的)
XGP.lazyLoading = (function(){
var uniqInstance;
function constructor(){
var attr = false;
function method(){
}
return {
attrp: true,
methodp: function(){
}
}
}
return {
getInstance: function(){
if(!uniqInstance){
uniqInstance = constructor();
}
return uniqInstance;
}
}
})();
6、分支技術(shù)
分支是一種用來(lái)把瀏覽器間的差異封裝在運(yùn)行期間進(jìn)行設(shè)置的動(dòng)態(tài)方法中的技術(shù)。
// 分支單例 (判斷程序的分支 <瀏覽器差異的檢測(cè)>)
var Ext = {} ;
var def = false ;
Ext.More = (function(){
var objA = { // 火狐瀏覽器 內(nèi)部的一些配置
attr1:'FF屬性1'
// 屬性1
// 屬性2
// 方法1
// 方法2
} ;
var objB = { // IE瀏覽器 內(nèi)部的一些配置
attr1:'IE屬性1'
// 屬性1
// 屬性2
// 方法1
// 方法2
} ;
return (def) ?objA:objB;
})();
alert(Ext.More.attr1);
比如說(shuō),如果網(wǎng)站中要頻繁使用xhr,每次調(diào)用都要再次運(yùn)行瀏覽器嗅探代碼,這樣會(huì)嚴(yán)重缺乏效率。更有效的做法是在腳本加載時(shí)一次性地確定針對(duì)瀏覽器的代碼。這正是分支技術(shù)所做的事情。當(dāng)然,分支技術(shù)并不總是更高效的選擇,在兩個(gè)或者多個(gè)分支中只有一個(gè)分支被用到了,其他分支就占用了內(nèi)存。
在考慮是否使用分支技術(shù)的時(shí)候,必須在縮短時(shí)間和占用更多內(nèi)存這一利一弊之間權(quán)衡一下。
下面利用分支技術(shù)實(shí)現(xiàn)XHR:
var XHR = (function(){
var standard = {
createXhrObj: function(){
return new XMLHttpRequest();
}
};
var activeXNew = {
createXhrObj: function(){
return new ActiveXObject('Msxml2.XMLHTTP');
}
};
var activeXOld = {
createXhrObj: function(){
return new ActiveXObject('Microsoft.XMLHTTP');
}
};
var testObj;
try{
testObj = standard.createXhrObj();
return testObj;
}catch(e){
try{
testObj = activeXNew.createXhrObj();
return testObj;
}catch(e){
try{
testObj = activeXOld.createXhrObj();
return testObj;
}catch(e){
throw new Error('No XHR object found in this environment.');
}
}
}
})();
7、單例模式的弊端
了解了這么多關(guān)于單例的知識(shí),我們?cè)賮?lái)看看它的弊端。
由于單例模式提供的是一種單點(diǎn)訪問(wèn),所以它有可能導(dǎo)致模塊間的強(qiáng)耦合。因此也就不利于單元測(cè)試了。
綜上,單例還是留給定義命名空間和實(shí)現(xiàn)分支型方法這些用途。
通過(guò)七點(diǎn)不同方面對(duì)單例模式的介紹,大家是不是對(duì)單例模式有了更深入的了解,希望這篇文章可以幫到大家。
12.命令模式(Command)
命令模式(Command)的定義是:用于將一個(gè)請(qǐng)求封裝成一個(gè)對(duì)象,從而使你可用不同的請(qǐng)求對(duì)客戶進(jìn)行參數(shù)化;對(duì)請(qǐng)求排隊(duì)或者記錄請(qǐng)求日志,以及執(zhí)行可撤銷的操作。也就是說(shuō)改模式旨在將函數(shù)的調(diào)用、請(qǐng)求和操作封裝成一個(gè)單一的對(duì)象,然后對(duì)這個(gè)對(duì)象進(jìn)行一系列的處理。此外,可以通過(guò)調(diào)用實(shí)現(xiàn)具體函數(shù)的對(duì)象來(lái)解耦命令對(duì)象與接收對(duì)象。
我們來(lái)通過(guò)車輛購(gòu)買程序來(lái)展示這個(gè)模式,首先定義車輛購(gòu)買的具體操作類:
var CarManager = {
// 請(qǐng)求信息
requestInfo: function (model, id) {
return 'The information for ' + model +
' with ID ' + id + ' is foobar';
},
// 購(gòu)買汽車
buyVehicle: function (model, id) {
return 'You have successfully purchased Item '
+ id + ', a ' + model;
},
// 組織view
arrangeViewing: function (model, id) {
return 'You have successfully booked a viewing of '
+ model + ' ( ' + id + ' ) ';
}
};
})();
來(lái)看一下上述代碼,通過(guò)調(diào)用函數(shù)來(lái)簡(jiǎn)單執(zhí)行manager的命令,然而在一些情況下,我們并不想直接調(diào)用對(duì)象內(nèi)部的方法。這樣會(huì)增加對(duì)象與對(duì)象間的依賴。現(xiàn)在我們來(lái)擴(kuò)展一下這個(gè)CarManager 使其能夠接受任何來(lái)自包括model和car ID 的CarManager對(duì)象的處理請(qǐng)求。根據(jù)命令模式的定義,我們希望實(shí)現(xiàn)如下這種功能的調(diào)用:
總結(jié)
命令模式比較容易設(shè)計(jì)一個(gè)命令隊(duì)列,在需求的情況下比較容易將命令計(jì)入日志,并且允許接受請(qǐng)求的一方?jīng)Q定是否需要調(diào)用,而且可以實(shí)現(xiàn)對(duì)請(qǐng)求的撤銷和重設(shè),而且由于新增的具體類不影響其他的類,所以很容易實(shí)現(xiàn)。
但敏捷開發(fā)原則告訴我們,不要為代碼添加基于猜測(cè)的、實(shí)際不需要的功能,如果不清楚一個(gè)系統(tǒng)是否需要命令模式,一般就不要著急去實(shí)現(xiàn)它,事實(shí)上,在需求的時(shí)通過(guò)重構(gòu)實(shí)現(xiàn)這個(gè)模式并不困難,只有在真正需求如撤銷、恢復(fù)操作等功能時(shí),把原來(lái)的代碼重構(gòu)為命令模式才有意義。
更多建議: