国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

常用的Javascript設計模式

2018-08-11 14:41 更新

1.策略模式(Strategy)


策略模式定義了算法家族,分別封裝起來,讓他們之間可以互相替換,此模式讓算法的變化不會影響到使用算法的客戶。


在理解策略模式之前,我們先來一個例子,一般情況下,如果我們要做數(shù)據(jù)合法性驗證,很多時候都是按照swith語句來判斷,但是這就帶來幾個問題,首先如果增加需求的話,我們還要再次修改這段代碼以增加邏輯,而且在進行單元測試的時候也會越來越復雜,代碼如下:


代碼如下:

        validator = {
            validate: function (value, type) {
                switch (type) {
                    case 'isNonEmpty ':
                        {
                            return true; // NonEmpty 驗證結果
                        }
                    case 'isNumber ':
                        {
                            return true; // Number 驗證結果
                            break;
                        }
                    case 'isAlphaNum ':
                        {
                            return true; // AlphaNum 驗證結果
                        }
                    default:
                        {
                            return true;
                        }
                }
            }
        };
        //  測試
        alert(validator.validate("123", "isNonEmpty"));

那如何來避免上述代碼中的問題呢,根據(jù)策略模式,我們可以將相同的工作代碼單獨封裝成不同的類,然后通過統(tǒng)一的策略處理類來處理,OK,我們先來定義策略處理類,代碼如下:

代碼如下:

var validator = {

    // 所有可以的驗證規(guī)則處理類存放的地方,后面會單獨定義
    types: {},

    // 驗證類型所對應的錯誤消息
    messages: [],

    // 當然需要使用的驗證類型
    config: {},

    // 暴露的公開驗證方法
    // 傳入的參數(shù)是 key => value對
    validate: function (data) {

        var i, msg, type, checker, result_ok;

        // 清空所有的錯誤信息
        this.messages = [];

        for (i in data) {
            if (data.hasOwnProperty(i)) {

                type = this.config[i];  // 根據(jù)key查詢是否有存在的驗證規(guī)則
                checker = this.types[type]; // 獲取驗證規(guī)則的驗證類

                if (!type) {
                    continue; // 如果驗證規(guī)則不存在,則不處理
                }
                if (!checker) { // 如果驗證規(guī)則類不存在,拋出異常
                    throw {
                        name: "ValidationError",
                        message: "No handler to validate type " + type
                    };
                }

                result_ok = checker.validate(data[i]); // 使用查到到的單個驗證類進行驗證
                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;
    }
};


然后剩下的工作,就是定義types里存放的各種驗證類了,我們這里只舉幾個例子:

代碼如下:

// 驗證給定的值是否不為空
validator.types.isNonEmpty = {
    validate: function (value) {
        return value !== "";
    },
    instructions: "傳入的值不能為空"
};

// 驗證給定的值是否是數(shù)字
validator.types.isNumber = {
    validate: function (value) {
        return !isNaN(value);
    },
    instructions: "傳入的值只能是合法的數(shù)字,例如:1, 3.14 or 2010"
};

// 驗證給定的值是否只是字母或數(shù)字
validator.types.isAlphaNum = {
    validate: function (value) {
        return !/[^a-z0-9]/i.test(value);
    },
    instructions: "傳入的值只能保護字母和數(shù)字,不能包含特殊字符"
};


使用的時候,我們首先要定義需要驗證的數(shù)據(jù)集合,然后還需要定義每種數(shù)據(jù)需要驗證的規(guī)則類型,代碼如下:

代碼如下:

var data = {
    first_name: "Tom",
    last_name: "Xu",
    age: "unknown",
    username: "TomXu"
};

validator.config = {
    first_name: 'isNonEmpty',
    age: 'isNumber',
    username: 'isAlphaNum'
};


最后,獲取驗證結果的代碼就簡單了:

代碼如下:

validator.validate(data);

if (validator.hasErrors()) {
    console.log(validator.messages.join("\n"));
}


總結

策略模式定義了一系列算法,從概念上來說,所有的這些算法都是做相同的事情,只是實現(xiàn)不同,他可以以相同的方式調用所有的方法,減少了各種算法類與使用算法類之間的耦合。

從另外一個層面上來說,單獨定義算法類,也方便了單元測試,因為可以通過自己的算法進行單獨測試。

實踐中,不僅可以封裝算法,也可以用來封裝幾乎任何類型的規(guī)則,是要在分析過程中需要在不同時間應用不同的業(yè)務規(guī)則,就可以考慮是要策略模式來處理各種變化。



2.裝飾者模式(Decorator)


裝飾者提供比繼承更有彈性的替代方案。 裝飾者用用于包裝同接口的對象,不僅允許你向方法添加行為,而且還可以將方法設置成原始對象調用(例如裝飾者的構造函數(shù))。
裝飾者用于通過重載方法的形式添加新功能,該模式可以在被裝飾者前面或者后面加上自己的行為以達到特定的目的。


那么裝飾者模式有什么好處呢?前面說了,裝飾者是一種實現(xiàn)繼承的替代方案。當腳本運行時,在子類中增加行為會影響原有類所有的實例,而裝飾者卻不然。取而代之的是它能給不同對象各自添加新行為。如下代碼所示:

代碼如下:


//需要裝飾的類(函數(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());
下面是另一個實例,當我們在裝飾者對象上調用performTask時,它不僅具有一些裝飾者的行為,同時也調用了下層對象的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();
再來一個徹底的例子:

代碼如下:


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í)行原型(這時候是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í)行原型(這時候是BlueBalls了)的decorate方法
        console.log('An angel on the top'); // 第5步 再輸出angel
        // 將這2步作為Angel的decorate方法
    }
};
tree = tree.getDecorator('BlueBalls'); // 第3步:將BlueBalls對象賦給tree,這時候父原型里的getDecorator依然可用
tree = tree.getDecorator('Angel'); // 第6步:將Angel對象賦給tree,這時候父原型的父原型里的getDecorator依然可用
tree = tree.getDecorator('RedBalls'); // 第9步:將RedBalls對象賦給tree
tree.decorate(); // 第10步:執(zhí)行RedBalls對象的decorate方法


總結
裝飾者模式是為已有功能動態(tài)地添加更多功能的一種方式,把每個要裝飾的功能放在單獨的函數(shù)里,然后用該函數(shù)包裝所要裝飾的已有函數(shù)對象,因此,當需要執(zhí)行特殊行為的時候,調用代碼就可以根據(jù)需要有選擇地、按順序地使用裝飾功能來包裝對象。優(yōu)點是把類(函數(shù))的核心職責和裝飾功能區(qū)分開了。



3.代理模式(Proxy)


代理,顧名思義就是幫助別人做事,GoF對代理模式的定義如下:

代理模式(Proxy),為其他對象提供一種代理以控制對這個對象的訪問。

代理模式使得代理對象控制具體對象的引用。代理幾乎可以是任何對象:文件,資源,內存中的對象,或者是一些難以復制的東西。


我們來舉一個簡單的例子,假如dudu要送酸奶小妹玫瑰花,卻不知道她的聯(lián)系方式或者不好意思,想委托大叔去送這些玫瑰,那大叔就是個代理(其實挺好的,可以扣幾朵給媳婦),那我們如何來做呢?


代碼如下:

// 先聲明美女對象
var girl = function (name) {
    this.name = name;
};

// 這是dudu
var dudu = function (girl) {
    this.girl = girl;
    this.sendGift = function (gift) {
        alert("Hi " + girl.name + ", dudu送你一個禮物:" + gift);
    }
};

// 大叔是代理
var proxyTom = function (girl) {
    this.girl = girl;
    this.sendGift = function (gift) {
        (new dudu(girl)).sendGift(gift); // 替dudu送花咯
    }
};

調用方式就非常簡單了:


代碼如下:

var proxy = new proxyTom(new girl("酸奶小妹"));
proxy.sendGift("999朵玫瑰");

實戰(zhàn)一把

通過上面的代碼,相信大家對代理模式已經非常清楚了,我們來實戰(zhàn)下:我們有一個簡單的播放列表,需要在點擊單個連接(或者全選)的時候在該連接下方顯示視頻曲介紹以及play按鈕,點擊play按鈕的時候播放視頻,列表結構如下:


代碼如下:

<h1>Dave Matthews vids</h1>
<p><span id="toggle-all">全選/反選</span></p>
<ol id="vids">
  <li><input type="checkbox" checked><a href="http://new.music.yahoo.com/videos/--2158073">Gravedigger</a></li>
  <li><input type="checkbox" checked><a href="http://new.music.yahoo.com/videos/--4472739">Save Me</a></li>
  <li><input type="checkbox" checked><a href="http://new.music.yahoo.com/videos/--45286339">Crush</a></li>
  <li><input type="checkbox" checked><a href="http://new.music.yahoo.com/videos/--2144530">Don't Drink The Water</a></li>
  <li><input type="checkbox" checked><a href="http://new.music.yahoo.com/videos/--217241800">Funny the Way It Is</a></li>
  <li><input type="checkbox" checked><a href="http://new.music.yahoo.com/videos/--2144532">What Would You Say</a>
</li>
</ol>

我們先來分析如下,首先我們不僅要監(jiān)控a連接的點擊事件,還要監(jiān)控“全選/反選”的點擊事件,然后請求服務器查詢視頻信息,組裝HTML信息顯示在li元素的最后位置上,效果如下:

JavaScript

然后再監(jiān)控play連接的點擊事件,點擊以后開始播放,效果如下:
w3c

好了,開始,沒有jQuery,我們自定義一個選擇器:


代碼如下:

var $ = function (id) {
    return document.getElementById(id);
};

由于Yahoo的json服務提供了callback參數(shù),所以我們傳入我們自定義的callback以便來接受數(shù)據(jù),具體查詢字符串拼裝代碼如下:

代碼如下:

var http = {
    makeRequest: function (ids, callback) {
        var url = 'http://query.yahooapis.com/v1/public/yql?q=',
            sql = 'select * from music.video.id where ids IN ("%ID%")',
            format = "format=json",
            handler = "callback=" + callback,
            script = document.createElement('script');

            sql = sql.replace('%ID%', ids.join('","'));
            sql = encodeURIComponent(sql);

            url += sql + '&' + format + '&' + handler;
            script.src = url;

        document.body.appendChild(script);
    }
};

代理對象如下:


代碼如下:

var proxy = {
    ids: [],
    delay: 50,
    timeout: null,
    callback: null,
    context: null,
    // 設置請求的id和callback以便在播放的時候觸發(fā)回調
    makeRequest: function (id, callback, context) {

        // 添加到隊列dd to the queue
        this.ids.push(id);

        this.callback = callback;
        this.context = context;

        // 設置timeout
        if (!this.timeout) {
            this.timeout = setTimeout(function () {
                proxy.flush();
            }, this.delay);
        }
    },
    // 觸發(fā)請求,使用代理職責調用了http.makeRequest
    flush: function () {
        // proxy.handler為請求yahoo時的callback
        http.makeRequest(this.ids, 'proxy.handler'); 
        // 請求數(shù)據(jù)以后,緊接著執(zhí)行proxy.handler方法(里面有另一個設置的callback)
        
        // 清楚timeout和隊列
        this.timeout = null;
        this.ids = [];

    },
    handler: function (data) {
        var i, max;

        // 單個視頻的callback調用
        if (parseInt(data.query.count, 10) === 1) {
            proxy.callback.call(proxy.context, data.query.results.Video);
            return;
        }

        // 多個視頻的callback調用
        for (i = 0, max = data.query.results.Video.length; i < max; i += 1) {
            proxy.callback.call(proxy.context, data.query.results.Video[i]);
        }
    }
};

視頻處理模塊主要有3種子功能:獲取信息、展示信息、播放視頻:


代碼如下:

var videos = {
    // 初始化播放器代碼,開始播放
    getPlayer: function (id) {
        return '' +
            '<object width="400" height="255" id="uvp_fop" allowFullScreen="true">' +
            '<param name="movie" value="http://d.yimg.com/m/up/fop/embedflv/swf/fop.swf"\/>' +
            '<param name="flashVars" value="id=v' + id + '&eID=1301797&lang=us&enableFullScreen=0&shareEnable=1"\/>' +
            '<param name="wmode" value="transparent"\/>' +
            '<embed ' +
            'height="255" ' +
            'width="400" ' +
            'id="uvp_fop" ' +
            'allowFullScreen="true" ' +
            'src="http://d.yimg.com/m/up/fop/embedflv/swf/fop.swf" ' +
            'type="application/x-shockwave-flash" ' +
            'flashvars="id=v' + id + '&eID=1301797&lang=us&ympsc=4195329&enableFullScreen=1&shareEnable=1"' +
            '\/>' +
            '<\/object>';
                },
    // 拼接信息顯示內容,然后在append到li的底部里顯示
    updateList: function (data) {
        var id,
            html = '',
            info;

        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í)行代理職責,并傳入videos.updateList回調函數(shù)
            return;
        }

        if (info.style.display === "none") {
            info.style.display = '';
        } else {
            info.style.display = 'none';
        }
    }
};

現(xiàn)在可以處理點擊事件的代碼了,由于有很多a連接,如果每個連接都綁定事件的話,顯然性能會有問題,所以我們將事件綁定在<ol>元素上,然后檢測點擊的是否是a連接,如果是說明我們點擊的是視頻地址,然后就可以播放了:


代碼如下:

$('vids').onclick = function (e) {
    var src, id;

    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];

    //如果點擊的是已經生產的視頻信息區(qū)域的連接play,就開始播放
    // 然后return不繼續(xù)了
    if (src.className === "play") {
        src.parentNode.innerHTML = videos.getPlayer(id);
        return;
    }
        
    src.parentNode.id = "v" + id;
    videos.getInfo(id); // 這個才是第一次點擊的時候顯示視頻信息的處理代碼
};

全選反選的代碼大同小異,我們就不解釋了:


代碼如下:

$('toggle-all').onclick = function (e) {

    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;
        }
        // 忽略沒有選擇的項
        if (!hrefs[i].parentNode.firstChild.checked) {
            continue;
        }

        id = hrefs[i].href.split('--')[1];
        hrefs[i].parentNode.id = "v" + id;
        videos.getInfo(id);
    }
};


總結

代理模式一般適用于如下場合:

1.遠程代理,也就是為了一個對象在不同的地址空間提供局部代表,這樣可以隱藏一個對象存在于不同地址空間的事實,就像web service里的代理類一樣。
2.虛擬代理,根據(jù)需要創(chuàng)建開銷很大的對象,通過它來存放實例化需要很長時間的真實對象,比如瀏覽器的渲染的時候先顯示問題,而圖片可以慢慢顯示(就是通過虛擬代理代替了真實的圖片,此時虛擬代理保存了真實圖片的路徑和尺寸。
3.安全代理,用來控制真實對象訪問時的權限,一般用于對象應該有不同的訪問權限。
4.智能指引,只當調用真實的對象時,代理處理另外一些事情。例如C#里的垃圾回收,使用對象的時候會有引用次數(shù),如果對象沒有引用了,GC就可以回收它了。



4.工廠模式(Factory)


工廠模式也是對象創(chuàng)建模式之一,它通常在類或類的靜態(tài)方法中去實現(xiàn)。構造對象的一種方式是使用new操作符,但使用new時正是針對實現(xiàn)編程,會造成“耦合”問題,與具體的類關系緊密。導致代碼更脆弱,缺乏彈性,在復雜邏輯的項目中建議是面向接口編程。 

先看簡單工廠模式 


代碼如下:

Person(name, age) { 
var obj = {} 
obj.name = name 
obj.age = age 
return obj 

var p1 = Person('jack', 25) 
var p2 = Person('lily', 22) 

與構造函數(shù)方式寫一個類的區(qū)別在于沒有使用this,而是每次都構造一個空對象,然后給其添加屬性。創(chuàng)建對象方式不是使用new,而是使用函數(shù)調用方式。這種方式基本上用來替代一個類(具有相同屬性的對象),而復雜一些的工廠則可以造不同類型的對象。 
下面以個水果工廠示例 

代碼如下:

function Banana() { 
this.price = '$1.5' 

function Apple() { 
this.price = '$1.2' 

function Orange() { 
this.price = '$2.2' 

// 靜態(tài)工廠類 
function Fruit() {} 
Fruit.factory = function(type) { 
if (!window[type]) { 
return 

var fruit = new window[type] 
return fruit 

// 制造不同的水果 
var banana = Fruit.factory('Banana') 
var apple = Fruit.factory('Apple') 
var orange = Fruit.factory('Orange') 

有三個水果類Banana、Apple、Orange,一個水果工廠類Fruit,通過靜態(tài)方法factory每次可以造出不同的水果類對象。 
工廠模式在JavaScript原生對象Object也有所體現(xiàn),比如 

代碼如下:

var obj = Object(), 
num = Object(1), 
str = Object('s'), 
boo = Object(false); 

Object就是一個工廠,根據(jù)參數(shù)不同會構造出不同的對象。obj是一個空對象,num是一個Number類型的對象,str是一個String類型的對象,boo是Boolean類型的對象。 
jQuery.Callbacks也是一個工廠,每次調用它都會返回一個具有add, remove, fire等方法的對象。還可以根據(jù)參數(shù)如“once”, “memory”等構造出具有不同性質的對象。 

所謂的工廠模式,是指可以返回一個對象的方法。 
利用這種模式,我們可以做什么呢?假設我不滿足現(xiàn)有的DOM對象里面所擁有的方法,我想要增加一個自定義的方法叫sayHello,我們可以這樣做: 

代碼如下:

function RemouldNodeObj(DomNode){ 
//先判斷一下傳遞進來的參數(shù)是不是一個Dom節(jié)點 
if(typeof DomNode == "object" && DomNode.nodeType == 1){ 
DomNode.say = function(){ 
alert("Hello!!"); 

}else{ 
alert("你傳遞進來的參數(shù)不正確!"); 



//這樣調用: 
window.onload = function(){ 
var oDiv = RemouldNodeObj(document.getElementById("test")); 
//通過這一步,oDiv就擁有了新的方法say 
oDiv.say(); 


有了上面的基礎后,我們來實現(xiàn)點復雜的功能,我們要實現(xiàn)只要通過js的調用就生成一個簡單的form表單,看代碼: 

代碼如下:

<html> 
<head> 
<title>JavaScript之工廠模式</title> 
<script type="text/javascript"> 
function RemouldNodeObj(DOMnode){ 
//先判斷一下傳遞進來的參數(shù)是不是一個Dom節(jié)點 
if(typeof DOMnode == "object" && DOMnode.nodeType == 1){ 
DOMnode.createForm = function(opt){ 
//下面是一大串的字符串加法,只是為了拼裝出form元素 
var oForm = ""; 
oForm += "<form action=\"" + opt.action + "\" "; 
oForm += "method=\"" + (opt.method || 'GET') + "\" id=\""; 
oForm += (opt.id || "") + "\""; 
oForm += "style=\"width:200px;height:30px;border:2px solid #223344\">"; 
oForm += "</form>"; 
//這里的this不要想得太復雜,誰調用就指向誰,所以this指向 oDiv 
this.innerHTML = oForm; 

}else{ 
alert("參數(shù)不正確!"); 

return DOMnode; 


//這樣調用 
window.onload = function(){ 
var oDiv = RemouldNodeObj(document.getElementById("custom")); 
oDiv.createForm({ 
'action' : 'index.jsp', 
'method' : 'post', 
'id' : 'myForm' 
}); 

</script> 
</head> 

<body> 
<div id="custom">###</div> 
</body> 
</html> 

看到了沒?這樣的調用方式是不是很像jQuery?如果能夠解決跨瀏覽器問題的話,其實完全可以做出一個搜索欄插件來!


5.模板模式(Template)

一、定義
模板方法是基于繼承的設計模式,可以很好的提高系統(tǒng)的擴展性。 java中的抽象父類、子類 
模板方法有兩部分結構組成,第一部分是抽象父類,第二部分是具體的實現(xiàn)子類。


二、示例
Coffee or Tea 
(1) 把水煮沸 
(2) 用沸水浸泡茶葉 
(3) 把茶水倒進杯子 
(4) 加檸檬

/* 抽象父類:飲料 */
var Beverage = function(){};
// (1) 把水煮沸
Beverage.prototype.boilWater = function() {
  console.log("把水煮沸");
};
// (2) 沸水浸泡
Beverage.prototype.brew = function() {
  throw new Error("子類必須重寫brew方法");
};
// (3) 倒進杯子
Beverage.prototype.pourInCup = function() {
  throw new Error("子類必須重寫pourInCup方法");
};
// (4) 加調料
Beverage.prototype.addCondiments = function() {
  throw new Error("子類必須重寫addCondiments方法");
};
 
/* 模板方法 */
Beverage.prototype.init = function() {
  this.boilWater();
  this.brew();
  this.pourInCup();
  this.addCondiments();
}
 
/* 實現(xiàn)子類 Coffee*/
var Coffee = function(){};
Coffee.prototype = new Beverage();
// 重寫非公有方法
Coffee.prototype.brew = function() {
  console.log("用沸水沖泡咖啡");
};
Coffee.prototype.pourInCup = function() {
  console.log("把咖啡倒進杯子");
};
Coffee.prototype.addCondiments = function() {
  console.log("加牛奶");
};
var coffee = new Coffee();
coffee.init();
通過模板方法模式,在父類中封裝了子類的算法框架。這些算法框架在正常狀態(tài)下是適用大多數(shù)子類的,但也會出現(xiàn)“個性”子類。 
如上述流程,加調料是可選的。 
鉤子方法可以解決這個問題,放置鉤子是隔離變化的一種常見手段。

/* 添加鉤子方法 */
Beverage.prototype.customerWantsCondiments = function() {
  return true;
};
Beverage.prototype.init = function() {
  this.boilWater();
  this.brew();
  this.pourInCup();
  if(this.customerWantsCondiments()) {
    this.addCondiments();
  }
}
 
/* 實現(xiàn)子類 Tea*/
var Tea = function(){};
Tea.prototype = new Beverage();
// 重寫非公有方法
Tea.prototype.brew = function() {
  console.log("用沸水沖泡茶");
};
Tea.prototype.pourInCup = function() {
  console.log("把茶倒進杯子");
};
Tea.prototype.addCondiments = function() {
  console.log("加牛奶");
};
Tea.prototype.customerWantsCondiments = function() {
  return window.confirm("需要添加調料嗎?");
};
var tea = new Tea();
tea.init();
JavaScript沒有提供真正的類式繼承,繼承是通過對象與對象之間的委托來實現(xiàn)的。


三、“好萊塢原則”:別調用我們,我們會調用你
典型使用場景: 
(1)模板方法模式:使用該設計模式意味著子類放棄了對自己的控制權,而是改為父類通知子類。作為子類,只負責提供一些設計上的細節(jié)。 
(2)觀察者模式:發(fā)布者把消息推送給訂閱者。 
(3)回調函數(shù):ajax異步請求,把需要執(zhí)行的操作封裝在回調函數(shù)里,當數(shù)據(jù)返回后,這個回調函數(shù)才被執(zhí)行。



6.外觀模式(Facade)


外觀模式(Facade)為子系統(tǒng)中的一組接口提供了一個一致的界面,此模塊定義了一個高層接口,這個接口值得這一子系統(tǒng)更加容易使用。


外觀模式不僅簡化類中的接口,而且對接口與調用者也進行了解耦。外觀模式經常被認為開發(fā)者必備,它可以將一些復雜操作封裝起來,并創(chuàng)建一個簡單的接口用于調用。

外觀模式經常被用于JavaScript類庫里,通過它封裝一些接口用于兼容多瀏覽器,外觀模式可以讓我們間接調用子系統(tǒng),從而避免因直接訪問子系統(tǒng)而產生不必要的錯誤。

外觀模式的優(yōu)勢是易于使用,而且本身也比較輕量級。但也有缺點 外觀模式被開發(fā)者連續(xù)使用時會產生一定的性能問題,因為在每次調用時都要檢測功能的可用性。

下面是一段未優(yōu)化過的代碼,我們使用了外觀模式通過檢測瀏覽器特性的方式來創(chuàng)建一個跨瀏覽器的使用方法。


代碼如下:

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;
    }
}; 

再來一個簡單的例子,說白了就是用一個接口封裝其它的接口:

代碼如下:

var mobileEvent = {
    // ...
    stop: function (e) {
        e.preventDefault();
        e.stopPropagation();
    }
    // ...
};

總結

那么何時使用外觀模式呢?一般來說分三個階段:

首先,在設計初期,應該要有意識地將不同的兩個層分離,比如經典的三層結構,在數(shù)據(jù)訪問層和業(yè)務邏輯層、業(yè)務邏輯層和表示層之間建立外觀Facade。

其次,在開發(fā)階段,子系統(tǒng)往往因為不斷的重構演化而變得越來越復雜,增加外觀Facade可以提供一個簡單的接口,減少他們之間的依賴。

第三,在維護一個遺留的大型系統(tǒng)時,可能這個系統(tǒng)已經很難維護了,這時候使用外觀Facade也是非常合適的,為系系統(tǒng)開發(fā)一個外觀Facade類,為設計粗糙和高度復雜的遺留代碼提供比較清晰的接口,讓新系統(tǒng)和Facade對象交互,F(xiàn)acade與遺留代碼交互所有的復雜工作。



7.建造者模式(Builder)


在軟件系統(tǒng)中,有時候面臨著“一個復雜對象”的創(chuàng)建工作,其通常由各個部分的子對象用一定的算法構成;由于需求的變化,這個復雜對象的各個部分經常面臨著劇烈的變化,但是將它們組合在一起的算法確相對穩(wěn)定。如何應對這種變化?如何提供一種“封裝機制”來隔離出“復雜對象的各個部分”的變化,從而保持系統(tǒng)中的“穩(wěn)定構建算法”不隨著需求改變而改變?這就是要說的建造者模式。

建造者模式可以將一個復雜對象的構建與其表示相分離,使得同樣的構建過程可以創(chuàng)建不同的表示。也就是說如果我們用了建造者模式,那么用戶就需要指定需要建造的類型就可以得到它們,而具體建造的過程和細節(jié)就不需要知道了。


這個模式相對來說比較簡單,先上代碼,然后再解釋


代碼如下:

function getBeerById(id, callback) {
    // 使用ID來請求數(shù)據(jù),然后返回數(shù)據(jù).
    asyncRequest('GET', 'beer.uri?id=' + id, function (resp) {
        // callback調用 response
        callback(resp.responseText);
    });
}

var el = document.querySelector('#test');
el.addEventListener('click', getBeerByIdBridge, false);

function getBeerByIdBridge(e) {
    getBeerById(this.id, function (beer) {
        console.log('Requested Beer: ' + beer);
    });
}

根據(jù)建造者的定義,表相即是回調,也就是說獲取數(shù)據(jù)以后如何顯示和處理取決于回調函數(shù),相應地回調函數(shù)在處理數(shù)據(jù)的時候不需要關注是如何獲取數(shù)據(jù)的,同樣的例子也可以在jquery的ajax方法里看到,有很多回調函數(shù)(比如success, error回調等),主要目的就是職責分離。

同樣再來一個jQuery的例子:


代碼如下:

$('<div class= "foo"> bar </div>');

我們只需要傳入要生成的HTML字符,而不需要關系具體的HTML對象是如何生產的。


總結

建造者模式主要用于“分步驟構建一個復雜的對象”,在這其中“分步驟”是一個穩(wěn)定的算法,而復雜對象的各個部分則經常變化,其優(yōu)點是:建造者模式的“加工工藝”是暴露的,這樣使得建造者模式更加靈活,并且建造者模式解耦了組裝過程和創(chuàng)建具體部件,使得我們不用去關心每個部件是如何組裝的。



8.觀察者模式(Observer)



觀察者模式有時也稱為發(fā)布--訂閱模式,在觀察者模式中,有一個觀察者可以管理所有的目標,等到有狀態(tài)發(fā)生改變的時候發(fā)出通知。(其實sql server中的發(fā)布訂閱也是這個道理)

假如以前村里的廣播是一個觀察者,那么每個村民就是被觀察對象,如果村子里有通知,政策發(fā)生改變的時候,就需要通過廣播把這個消息發(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) {//這里將村子建一個這種廣播方式 
for (var i in this) { 
o[i] = this[i]; 


}; 
var village1 = {}; 
observer.make(village1);//將村子1建立這種觀察者模式 
var villager11 = { 
read: function (what) { 
console.log('我是第一個村子的第一個村名:' + what); 

}; 
var villager12 = { 
read: function (what) { 
console.log('我是第一個村子的第二個村名:'+what); 

}; 
village1.addVillager(villager11.read); 
village1.addVillager(villager12.read); 
village1.publish('大家來開會呀?。?!'); 
village1.removeVillager(villager11.read); 
village1.publish('大家來開會呀?。?!'); 
/* 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('我是第二個村子的第一個村名:' + what); 

}; 
var villager22 = { 
read: function (what) { 
console.log('我是第二個村子的第二個村名:'+what); 

}; 
village2.myAddVillager(villager21.read); 
village2.myAddVillager(villager22.read); 
village2.myPublish('大家來領豬肉了!?。?#39;);*/
</script> 
</body> 
</html>


寫到這里觀察者模式實現(xiàn)了,但是可能會有多個村子需要這種模式,那我們這里將observer改造成構造函數(shù)的方式

<!DOCTYPE html> 
<html lang="en"> 
<head> 
<meta charset="UTF-8"> 
<title>觀察者模式</title> 
</head> 
<body> 
<script> 
function Observer(){//觀察者,這里采用構造函數(shù),可以對不同村落進行使用 
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) {//這里將村子建一個這種廣播方式 
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('我是第一個村子的第一個村名:' + what); 

}; 
var villager12 = { 
read: function (what) { 
console.log('我是第一個村子的第二個村名:'+what); 

}; 
village1.addVillager(villager11.read); 
village1.addVillager(villager12.read); 
village1.publish('大家來開會呀?。?!'); 
village1.removeVillager(villager11.read); 
village1.publish('大家來開會呀?。?!'); 
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('我是第二個村子的第一個村名:' + what); 

}; 
var villager22 = { 
read: function (what) { 
console.log('我是第二個村子的第二個村名:'+what); 

}; 
village2.myAddVillager(villager21.read); 
village2.myAddVillager(villager22.read); 
village2.myPublish('大家來領豬肉了?。。?#39;); 
</script> 
</body> 
</html>



9.抽象工廠模式(Abstract Factory)


抽象工廠模式說明

1. 工廠方法模式的問題: 在工廠方法模式里,創(chuàng)建類都需要通過 工廠類,如果要擴展程序,就必須修改工廠類,這違背了閉包原則,對擴展開放,對修改關閉;對于設計有一定的問題。
2. 如何解決:就要用到抽象工廠模式,就是對功能類單獨創(chuàng)建工廠類,這樣就不必修改之前的代碼,又擴展了功能。
3. 工廠模式其實就是對 實現(xiàn)同一接口的 實現(xiàn)類 的 統(tǒng)一 工廠方式創(chuàng)建調用,但 javascript 沒有接口這號東西,所以就去掉這一層 實現(xiàn),但位功能類的成員及方法都應當一樣;


抽象工廠源碼例子

1. 郵件發(fā)送類:


代碼如下:

function MailSender() {
    this.to = '';
    this.title = '';
    this.content = '';
}

MailSender.prototype.send = function() {
    //send body
}

2. 短信發(fā)送類:


代碼如下:

function SmsSender() {
    this.to = '';
    this.title = '';
    this.content = '';
}

SmsSender.prototype.send = function() {
    //send body
}

3. 這里本來是創(chuàng)建工廠接口類,這里就去掉了; 直接創(chuàng)建各功能類工廠;

1>. 郵件工廠類:


代碼如下:

function MailFactory() {
    
}
MailFactory.prototype.produce = function() {
    return new MailSender();
}


2>. 短信工廠類:


代碼如下:

function SmsFactory() {
    
}
SmsFactory.prototype.produce = function() {
    return new SmsSender();
}


4. 使用方法:


代碼如下:

var factory = new MailFactory();
var sender = factory.produce();
sender.to = 'toname#mail.com';
sender.title = '抽象工廠模式';
sender.content = '發(fā)送內容';
sender.send();

其他說明

在面向對象語言如 java,.net C# 使用的工廠模式,都用到接口,接口是對外向各種用戶暴露的可用方法,說明這個功能應用有些什么的方法應用,用戶應該怎么用這個接口。對象以類的形式表現(xiàn)出來,代表現(xiàn)實世界中的某種抽象,也許場景會有很多類似的應用,比如上面的 郵件發(fā)送,短信發(fā)送,再比如商場中的各種促銷手段,以及動物世界中的各種飛禽走獸等..


如果我們不以接口形式提供用戶使用,勢必提供暴露真實的功能類對象給用戶,用戶可以隨意對類對象進行修改跟擴展,這是不允許的。

工廠方法模式 跟 抽象工廠模式可以很好的解決這樣的問題,用戶只能使用接口調用工廠類,來進行規(guī)定的操作;抽象工廠模式更進一步使用擴展功能變得容易,功能類跟工廠類都在實現(xiàn)相應的接口上實現(xiàn)各自類級別的擴展,不會涉及修改到其他的類或方法;



10.適配器模式(Adapter)


說明: 適配器模式,一般是為要使用的接口,不符本應用或本系統(tǒng)使用,而需引入的中間適配層類或對象的情況;


場景: 就好比我們買了臺手機,買回來后發(fā)現(xiàn),充電線插頭是三插頭,但家里,只有兩插頭的口的插座,怎么辦?為了方便,也有為能在任何地方都能充上電,就得去買個通用充電適配器; 這樣手機才能在自己家里充上電;不然只能放著,或跑到有這個插頭的地方充電;


實際開發(fā)環(huán)境下,由于舊的系統(tǒng),或第三方應用提供的接口,與我們定義的接口不匹配,在以面向接口編程的環(huán)境下,就無法使用這樣舊的,或第三方的接口,這時我們就使用適配類繼承待適匹配的類,并讓適配類實現(xiàn)接口的方式來引入舊的系統(tǒng)或第三方應用的接口;


這樣使用接口編程時,就可以使用這個適匹配類,來間接調用舊的系統(tǒng)或第三方應用的接口。


在 Javascript 要實現(xiàn)類似動態(tài)面向對象語言的適配器模式的代碼,可以使用到 prototype 的繼承實例來實現(xiàn);因為是基于接口約束的,但是Javascript沒有接口這號東西,我們去掉接口這一層,直接實現(xiàn)接口實現(xiàn)類 Target ,模擬類似的源碼出來;


源碼實例

1. 待適配的類及接口方法:


代碼如下:


function Adaptee() {
    this.name = 'Adaptee';
}
Adaptee.prototype.getName = function() {
    return this.name;
}

2. 普通實現(xiàn)類 [由于 Javascript 中沒有接口,所以就直接提供實現(xiàn)類]


代碼如下:

function Target() {
    this.name = 'Target';
}

Target.prototype.queryName= function() {
    return this.name;
}

3. 適配類:


代碼如下:

function Adapte() {
    this.name = '';
}

Adapte.prototype = new Adaptee();

Adapte.prototype.queryName = function() {
    this.getName();
}

4.使用方法:


代碼如下:

var local = new Target();
local.queryName(); //調用普通實現(xiàn)類

var adapte = new Adapte();
adapte.queryName(); //調用舊的系統(tǒng)或第三方應用接口;


其他說明

上面第四步,var local 以及 var adapte 類似像 Java,C# 這樣的面向對象語言中接口引用指定,如:


代碼如下:

interface Target {
    public String queryName();
}
//接口引用指向
Target local = new RealTarget(); //即上面 Javascript 的 Target 實現(xiàn)類
local.queryName();

//適配器
Target adapte = new Adapte();
adapte.queryName();

可見適配器類是連接接口與目標類接口的中間層;就是用來解決,需要的目標已經存在了,但我們無法直接使用,不能跟我們的代碼定義協(xié)同使用,就得使用適器模式,適配器模式也叫轉換模式,包裝模式;



11.單例模式(Singleton)


單例模式的定義:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。


單例模式是一種常用的模式,有一些對象我們往往只需要一個,比如線程池、全局緩存、瀏覽器的window對象。在js開發(fā)中,單例模式的用途同樣非常廣泛。試想一下,當我們單擊登錄按鈕的時候,頁面中會出現(xiàn)一個登錄框,而這個浮窗是唯一的,無論單擊多少次登錄按鈕,這個浮窗只會被創(chuàng)建一次。因此這個登錄浮窗就適合用單例模式。


1、單例模式的使用場景
在使用一種模式之前,我們最好要知道,這種模式的使用場景。用了這么久的單例模式,竟全然不知!用它具體有哪些好處呢?
1).可以用它來劃分命名空間(這個就是就是經常用的了)
2).利用分支技術來封裝瀏覽器之間的差異(這個還真沒用過,挺新鮮)
3).借助單例模式,可以把代碼組織的更為一致,方便閱讀與維護(這個也用過了)


2、最基本的單例模式
最簡單的單例其實就是一個對象字面量。它把一批有一定關聯(lián)的方法和屬性組織在一起。

var Singleton = {
  attr1: true , 
  attr2: 10 ,
  method1 : function(){
    alert('我是方法1');
  },
  method2 : function(){
    alert('我是方法2');
  }
};
這個對象可以被修改。你可以添加屬性和方法。你也可以用delete運算符刪除現(xiàn)有成員。這實際上違背了面向對象設計的一條原則:類可以被擴展,但不應該被修改。如果某些變量需要保護,那么可以將其定義在閉包中。
對象字面量只是創(chuàng)建單例的方法之一。也并非所有的對象字面量都是單例,那些只是用來模仿關聯(lián)數(shù)組或容納數(shù)據(jù)的對象字面量顯然不是單例。

3、借用閉包創(chuàng)建單例
閉包主要的目地 保護數(shù)據(jù)

// 命名空間
var BHX = {} ;
BHX.Singleton = (function(){
  // 添加自己的私有成員
  var a1 = true ;
  var a2 = 10 ;
  var f1 = function(){
    alert('f1');
  }
  var f2 = function(){
    alert('f2');
  }        
  // 把塊級作用域里的執(zhí)行結果賦值給我的單例對象
  return {
      attr1: a1 , 
      attr2: a2 ,
      method1 : function(){
        return f1();
      },
      method2 : function(){
        return f2();
      }            
  } ;
})();
 
alert(BHX.Singleton.attr1);
BHX.Singleton.method1();
這種單例模式又稱模塊模式,指的是它可以把一批相關的方法和屬性組織為模塊并起到劃分命名空間的作用。

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;
這樣以來,即使我們在外面聲明了相同的變量,也能在一定程度上防止attr1的被修改。

2)、防止其它來源代碼的修改
現(xiàn)在網(wǎng)頁上的JavaScript代碼往往不止用一個來源,什么庫代碼、廣告代碼和徽章代碼。為了避免與自己代碼的沖突,可以定義一個包含自己所有代碼的對象。

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)頁的網(wǎng)站中,有些代碼是所有網(wǎng)頁都要用到的,他們通常被存放在獨立的文件中;而有些代碼則是某個網(wǎng)頁專用的,不會被用到其他地方。最好把這兩種代碼分別包裝在自己的單例對象中。
我們經常要用Javascript為表單添加功能。出于平穩(wěn)退化方面的考慮,通常先創(chuàng)建一個不依賴于Javascript的、使用普通提交機制完成任務的純HTML網(wǎng)頁。

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、惰性單例
前面所講的單例模式又一個共同點:單例對象都是在腳本加載時被創(chuàng)建出來。對于資源密集的或配置開銷甚大的單例,更合理的做法是將其實例化推遲到需要使用他的時候。
這種技術就是惰性加載(lazy loading)。
實現(xiàn)步驟如下:
1).將所有代碼移到constructor方法中
2).全權控制調用時機(正是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、分支技術
分支是一種用來把瀏覽器間的差異封裝在運行期間進行設置的動態(tài)方法中的技術。

// 分支單例 (判斷程序的分支 <瀏覽器差異的檢測>)
var Ext = {} ;
var def = false ;
Ext.More = (function(){
  var objA = {    // 火狐瀏覽器 內部的一些配置
      attr1:'FF屬性1'
      // 屬性1 
      // 屬性2 
      // 方法1 
      // 方法2
  } ;
  var objB = {    // IE瀏覽器 內部的一些配置
      attr1:'IE屬性1'
      // 屬性1 
      // 屬性2 
      // 方法1 
      // 方法2             
  } ;
  return (def) ?objA:objB;
})();
alert(Ext.More.attr1);
比如說,如果網(wǎng)站中要頻繁使用xhr,每次調用都要再次運行瀏覽器嗅探代碼,這樣會嚴重缺乏效率。更有效的做法是在腳本加載時一次性地確定針對瀏覽器的代碼。這正是分支技術所做的事情。當然,分支技術并不總是更高效的選擇,在兩個或者多個分支中只有一個分支被用到了,其他分支就占用了內存。
在考慮是否使用分支技術的時候,必須在縮短時間和占用更多內存這一利一弊之間權衡一下。
下面利用分支技術實現(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、單例模式的弊端
了解了這么多關于單例的知識,我們再來看看它的弊端。
由于單例模式提供的是一種單點訪問,所以它有可能導致模塊間的強耦合。因此也就不利于單元測試了。
綜上,單例還是留給定義命名空間和實現(xiàn)分支型方法這些用途。
通過七點不同方面對單例模式的介紹,大家是不是對單例模式有了更深入的了解,希望這篇文章可以幫到大家。



12.命令模式(Command)


命令模式(Command)的定義是:用于將一個請求封裝成一個對象,從而使你可用不同的請求對客戶進行參數(shù)化;對請求排隊或者記錄請求日志,以及執(zhí)行可撤銷的操作。也就是說改模式旨在將函數(shù)的調用、請求和操作封裝成一個單一的對象,然后對這個對象進行一系列的處理。此外,可以通過調用實現(xiàn)具體函數(shù)的對象來解耦命令對象與接收對象。


我們來通過車輛購買程序來展示這個模式,首先定義車輛購買的具體操作類:


代碼如下:

$(function () {

    var CarManager = {

        // 請求信息
        requestInfo: function (model, id) {
            return 'The information for ' + model +
        ' with ID ' + id + ' is foobar';
        },

        // 購買汽車
        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 + ' ) ';
        }
    };
})();

來看一下上述代碼,通過調用函數(shù)來簡單執(zhí)行manager的命令,然而在一些情況下,我們并不想直接調用對象內部的方法。這樣會增加對象與對象間的依賴?,F(xiàn)在我們來擴展一下這個CarManager 使其能夠接受任何來自包括model和car ID 的CarManager對象的處理請求。根據(jù)命令模式的定義,我們希望實現(xiàn)如下這種功能的調用:


代碼如下:

CarManager.execute({ commandType: "buyVehicle", operand1: 'Ford Escort', operand2: '453543' });

根據(jù)這樣的需求,我們可以這樣啦實現(xiàn)CarManager.execute方法:

代碼如下:

CarManager.execute = function (command) {
    return CarManager[command.request](command.model, command.carID);
};

改造以后,調用就簡單多了,如下調用都可以實現(xiàn)(當然有些異常細節(jié)還是需要再完善一下的):

代碼如下:

CarManager.execute({ request: "arrangeViewing", model: 'Ferrari', carID: '145523' });
CarManager.execute({ request: "requestInfo", model: 'Ford Mondeo', carID: '543434' });
CarManager.execute({ request: "requestInfo", model: 'Ford Escort', carID: '543434' });
CarManager.execute({ request: "buyVehicle", model: 'Ford Escort', carID: '543434' });

總結

命令模式比較容易設計一個命令隊列,在需求的情況下比較容易將命令計入日志,并且允許接受請求的一方決定是否需要調用,而且可以實現(xiàn)對請求的撤銷和重設,而且由于新增的具體類不影響其他的類,所以很容易實現(xiàn)。

但敏捷開發(fā)原則告訴我們,不要為代碼添加基于猜測的、實際不需要的功能,如果不清楚一個系統(tǒng)是否需要命令模式,一般就不要著急去實現(xiàn)它,事實上,在需求的時通過重構實現(xiàn)這個模式并不困難,只有在真正需求如撤銷、恢復操作等功能時,把原來的代碼重構為命令模式才有意義。



以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號