JavaScript 設計模式之代理模式

什麼是代理模式?

首先我們先看一個有趣的例子

在四月一個晴朗的早晨,小明遇見了他的百分百女孩,我們暫且稱呼小明的女神爲A。兩天之後,小明決定給A送一束花來表白。剛好小明打聽到A和他有一個共同的朋友B,於是內向的小明決定讓B來代替自己完成送花這件事情。雖然小明的故事必然以悲劇收場,因爲追MM更好的方式是送一輛寶馬。不管怎樣,我們還是先用代碼來描述一下小明追女神的過程,先看看不用代理模式的情況:

// 首先的有花的實例
var Flower = function(){};
var xiaoming = {
    // 送花的方法,參數爲送花目標
    sendFlower: function( target ){
        // 創建一個花的實例
        var flower = new Flower();
        // 送給目標
        target.receiveFlower( flower );
    }
};
// 女神
var A = {
    // 女神接收到了花
    receiveFlower: function( flower ){
        console.log( '收到花 ' + flower );
    }
};
// 小明開始送花
xiaoming.sendFlower( A );

接下來,我們引入代理B,即小明通過B來給A送花:

// 首先的有花的實例
var Flower = function(){};
var xiaoming = {
    // 送花的方法,參數爲送花目標
    sendFlower: function( target ){
        // 創建一個花的實例
        var flower = new Flower();
        // 送給目標
        target.receiveFlower( flower );
    }
};
// 女神閨蜜
var B = {
    // 小明把花送給了女神閨蜜,由他轉送
    receiveFlower: function( flower ){
        // 女神閨蜜把花送給了女神
        A.receiveFlower(flower)
    }
};
// 女神
var A = {
    // 女神接收到了花
    receiveFlower: function( flower ){
        console.log( '收到花 ' + flower );
    }
};
// 小明開始送花
xiaoming.sendFlower( A );

很顯然,執行結果跟第一段代碼一致,至此我們就完成了一個最簡單的代理模式的編寫。

也許讀者會疑惑,小明自己去送花和代理B幫小明送花,二者看起來並沒有本質的區別,引入一個代理對象看起來只是把事情搞複雜了而已。

但是,假設當A在心情好的時候收到花,小明表白成功的機率有60%,而當A在心情差的時候收到花,小明表白的成功率無限趨近於0。

小明跟A剛剛認識兩天,還無法辨別A什麼時候心情好。如果不合時宜地把花送給A,花被直接扔掉的可能性很大,這束花可是小明吃了7天泡麪換來的。

但是A的朋友B卻很瞭解A,所以小明只管把花交給B,B會監聽A的心情變化,然後選擇A心情好的時候把花轉交給A,代碼如下:

// 首先的有花的實例
var Flower = function(){};
var xiaoming = {
    // 送花的方法,參數爲送花目標
    sendFlower: function( target ){
        // 創建一個花的實例
        var flower = new Flower();
        // 送給目標
        target.receiveFlower( flower );
    }
};
// 女神閨蜜
var B = {
    // 小明把花送給了女神閨蜜,由他轉送
    receiveFlower: function( flower ){
        // 女神閨蜜看到女神心情好把花送給了女神
       A.listenGoodMood(function(){ // 監聽A的好心情
            A.receiveFlower( flower );
        });
    }
};
// 女神
var A = {
    // 女神接收到了花
    receiveFlower: function( flower ){
        console.log( '收到花 ' + flower );
    },
    // 女神心情好了
    listenGoodMood: function( fn ){
        setTimeout(function(){ // 假設10秒之後A的心情變好
            fn();
        }, 10000 );
    }
};
// 小明開始送花
xiaoming.sendFlower( A );

虛擬代理

虛擬代理把一些開銷很大的對象,延遲到真正需要它的時候纔去創建。

虛擬代理實現圖片預加載

在Web開發中,圖片預加載是一種常用的技術,如果直接給某個img標籤節點設置src屬性, 由於圖片過大或者網絡不佳,圖片的位置往往有段時間會是一片空白。常見的做法是先用一張loading圖片佔位,然後用異步的方式加載圖片,等圖片加載好了再把它填充到img節點裏,這種場景就很適合使用虛擬代理。

下面我們來實現這個虛擬代理,首先創建一個普通的本體對象,這個對象負責往頁面中創建一個img標籤,並且提供一個對外的setSrc接口,外界調用這個接口,便可以給該img標籤設置src屬性:

var myImage = (function(){
    var imgNode = document.createElement( 'img' );
    document.body.appendChild( imgNode );
    return {
        setSrc: function( src ){
        i   mgNode.src = src;
        }
    }
})();
myImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg')

引入虛擬代理模式

var myImage = (function(){
    var imgNode = document.createElement( 'img' );
    document.body.appendChild( imgNode );
    return {
        setSrc: function( src ){
            imgNode.src = src;
        }
    }
})();
var proxyImage = (function(){
    var img = new Image;
    img.onload = function(){
        myImage.setSrc( this.src );
    }
    return {
        setSrc: function( src ){
         myImage.setSrc( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' );
         img.src = src;
        }
    }
})();
proxyImage.setSrc( 'http://imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );

現在我們通過proxyImage間接地訪問MyImage。proxyImage控制了客戶對MyImage的訪問,並且在此過程中加入一些額外的操作,比如在真正的圖片加載好之前,先把img節點的src設置爲一張本地的loading圖片。

緩存代理

緩存代理可以爲一些開銷大的運算結果提供暫時的存儲,在下次運算時,如果傳遞進來的參數跟之前一致,則可以直接返回前面存儲的運算結果。

緩存代理的例子——計算乘積

var mult = function(){
    console.log( '開始計算乘積' );
    var a = 1;
    for ( var i = 0, l = arguments.length; i < l; i++ ){
        a = a * arguments[i];
    }
    return a;
};
// 加入緩存代理
var proxyMult = (function(){
    var cache = {};
    return function(){
        var args = Array.prototype.join.call( arguments, ',' );
        if ( args in cache ){
            return cache[ args ];
        }
        return cache[ args ] = mult.apply( this, arguments );
    }
})();
proxyMult( 1, 2, 3, 4 ); // 輸出:24
proxyMult( 1, 2, 3, 4 ); // 輸出:24

當我們第二次調用proxyMult(1,2,3,4)的時候,本體mult函數並沒有被計算,proxyMult直接返回了之前緩存好的計算結果。

通過增加緩存代理的方式,mult函數可以繼續專注於自身的職責——計算乘積,緩存的功能是由代理對象實現的。

其他代理模式

  • 防火牆代理:控制網絡資源的訪問,保護主題不讓“壞人”接近。
  • 遠程代理:爲一個對象在不同的地址空間提供局部代表
  • 保護代理:用於對象應該有不同訪問權限的情況。
  • 智能引用代理:取代了簡單的指針,它在訪問對象時執行一些附加操作,比如計算一個對象被引用的次數。
  • 寫時複製代理:通常用於複製一個龐大對象的情況。寫時複製代理延遲了複製的過程,當對象被真正修改時,纔對它進行復制操作。寫時複製代理是虛擬代理的一種變體,DLL(操作系統中的動態鏈接庫)是其典型運用場景。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章