js 設計模式——代理模式


代理模式

代理模式是為一個對象提供一個代用品或占位符,以便控制對它的訪問。

生活中有很多的代理模式的場景。例如,明星有經紀人作為代理,老板有秘書作為代理等等,當有事情的時候,會找到經紀人或秘書,再由他們轉達給明星或者老板。

首先通過一個例子來簡單的了解了解,故事是這樣的...

以下故事純屬虛構,不要當真

大家都知道三顧茅廬(不知道的百度一下)吧,諸葛亮何許人也,厲害的不要不要的,名聲在外啊。好巧不巧劉備知道了,劉備心想:“這么厲害的人跟着我,豈不美哉,統一三國起步指日可待”,於是,劉備帶着禮物就去請人家了。正常流程應該這樣:

// 劉備
let bei = {
  // 邀請
  invite(){
    liang.reception('草鞋')
  }
}
// 亮
let liang = {
   // 收到禮物
   reception(gift){
     console.log('亮收到禮物:' + gift)
   }
}
// 調用方法
bei.invite()

但是呢,事實不是這樣的,諸葛亮心想:“劉備一賣草鞋的就想見我?”,於是呢,劉備只見到了門童:

// 劉備
let bei = {
  // 邀請
  invite(){
    mentong.reception('草鞋')
  }
}
// 門童
let mentong = {
  // 接收禮物
  reception(gift){
    console.log('門童收到禮物:' + gift)
    // 給諸葛亮
    liang.reception('草鞋')
  }
}
// 亮
let liang = {
  // 接收禮物
  reception(gift){
    console.log('亮收到禮物:' + gift)
  }
}
// 調用方法
bei.invite()

所以,劉備就只能把禮物給了門童,門童在交給了諸葛亮,然后諸葛亮一看,好家伙,草鞋。。。

到此可以看成一個簡單的代理了

保護代理和虛擬代理

保護代理

諸葛亮收到草鞋后也是無語,然后叫來門童告訴他:“以后呢,送草鞋的,你就不用給我了,自己看着處理就好了”,門童心領神會,表示ojbk

// 門童
let mentong = {
  // 接收禮物
  reception(gift){
    console.log('門童收到禮物:' + gift)
    if(gift !== '草鞋'){
      // 給諸葛亮
      liang.reception(gift)
    }
  }
}

通過代理(門童)來處理一些不必要的東西,過濾掉無用信息,這可以理解為 保護代理

但在 JavaScript 並不容易實現保護代理,因為我們無法判斷誰訪問了某個對象。

虛擬代理

話題又回到劉備這,劉備這連着送禮都兩天了,也見不到人。有人就給他出了個方法。於是啊就去找到門童說:“小兄弟這錢你拿着,你幫我買點禮物給諸葛先生”,門童也是詫異什么時候變聰明了

// 門童
let mentong = {
  // 接收禮物
  reception(){
    // 拿錢去買禮物
    let book = new Book()
    // 給諸葛亮
    liang.reception(book)
  }
}

諸葛亮這回挺開心,於是就答應見劉備了(~ ̄▽ ̄)~

這可以理解為 虛擬代理

虛擬代理實現圖片預加載

注:I know 這個例子大家可能都看過了,因為沒有想到更好的例子(想到了更改補上),但是這個例子我會一行一行講解清楚的o( ̄▽ ̄)ブ

平時由於網絡的不佳,導致圖片出來前會有一片空白。所以我們限用一張 loading 圖片占位,在異步方式加載圖片

沒用代理

// 創建一個本體對象
var myImage = (function(){
  // 創建標簽
  var imgNode = document.createElement( 'img' );
  // 添加到頁面
  document.body.appendChild( imgNode );
  return {
    // 設置圖片的src
    setSrc: function( src ){
      // 更改src
      imgNode.src = src;
    }
  }
})();

myImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );

引入代理對象

// 創建一個本體對象
var myImage = (function(){
  // 創建標簽
  var imgNode = document.createElement( 'img' );
  // 添加到頁面
  document.body.appendChild( imgNode );
  return {
    // 設置圖片的src
    setSrc: function( src ){
      // 更改src
      imgNode.src = src;
    }
  }
})();

// 創建代理對象
var proxyImage = (function(){
  // 創建一個新的img標簽
  var img = new Image;
  // img 加載完成事件
  img.onload = function(){
    // 調用 myImage 替換src方法
    myImage.setSrc( this.src );
  }
  return {
    // 代理設置地址
    setSrc: function( src ){
      // 預加載 loading
      myImage.setSrc( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' );
      // 賦值正常圖片地址
      img.src = src;
    }
  }
})();

proxyImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );

現在我們可以通過 proxyImage 間接地訪問 MyImageproxyImage 控制了客戶對 MyImage 的訪問,並
且在此過程中提前把 img 節點的 src 設置為了一張本地的 loading 圖片


現在再來看看不用代理來實現預加載

// 創建一個本體對象
var MyImage = (function(){
  // 創建標簽
  var imgNode = document.createElement( 'img' );
  // 添加到頁面
  document.body.appendChild( imgNode );
  //  創建一個新的img標簽
  var img = new Image;
  // img 加載完成
  img.onload = function(){
    // 替換地址
    imgNode.src = img.src;
  };
  return {
    // 設置地址
    setSrc: function( src ){
      // 本地 loading 圖片地址
      imgNode.src = 'file:// /C:/Users/svenzeng/Desktop/loading.gif';
      // 賦值正常圖片地址
      img.src = src;
    }
  }
})();

MyImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );

現在來看看沒有用代理的代碼

  • 違反了單一原則。MyImage除了要負責img節點的設置,還要負責預加載圖片。這導致在處理其中一個職責時會因其強耦合性影響另一個職責。
  • 違反了開閉原則。倘若以后要去掉預加載,只能去更改MyImage對象,這不符合開閉原則。

代理和本體接口的一致性

如果有一天我們不再需要預加載,那么就不再需要代理對象,可以選擇直接請求本體。其中關鍵是代理對象和本體都對外提供了 setSrc 方法,在客戶看來,代理對象和本體是一致的, 代理接手請求的過程對於用戶來說是透明的,用戶並不清楚代理和本體的區別這樣做有兩個好處:

  • 用戶可以放心地請求代理,他只關心是否能得到想要的結果。
  • 在任何使用本體的地方都可以替換成使用代理。(是不是有點里氏替換的味道)
// 預加載
proxyImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );

// 不用預加載
myImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );

緩存代理

再舉一個經典的例子啊(沒想到別的,以后想到了再換(~ ̄▽ ̄)~)

乘積函數

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

我們也可以動態創建代理

/**************** 計算乘積 *****************/
var mult = function(){
  var a = 1;
  for ( var i = 0, l = arguments.length; i < l; i++ ){
    a = a * arguments[i];
  }
  return a;
}
/**************** 計算加和 *****************/
var plus = function(){
  var a = 0;
  for ( var i = 0, l = arguments.length; i < l; i++ ){
    a = a + arguments[i];
  }
  return a;
}
/**************** 創建緩存代理的工廠 *****************/
var createProxyFactory = function( fn ){
  // 緩存結果
  var cache = {};
  return function(){
    // 將參數轉換成字符串
    var args = Array.prototype.join.call( arguments, ',' );
    // 遍歷緩存結果如果存在直接返回結果
    if ( args in cache ){
      return cache[ args ];
    }
    // 不存在進行相應的計算並保存結果
    return cache[ args ] = fn.apply( this, arguments );
  }
};

// 創建乘法和加法
var proxyMult = createProxyFactory( mult ),proxyPlus = createProxyFactory( plus )

alert ( proxyMult( 1, 2, 3, 4 ) ); // 輸出:24 
alert ( proxyMult( 1, 2, 3, 4 ) ); // 輸出:24 
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 輸出:10 
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 輸出:10

代理模式包括許多小分類,在JavaScript開發中最常用的是虛擬代理和緩存代理。雖然代理模式非常有用,但我們在編寫業務代碼的時候,往往不需要去預先猜測是否需要使用代理模。當真正發現不方便直接訪問某個對象的時候,再編寫代理也不遲。

目前對於代理模式的理解就這么多,以后有了新的理解會繼續更新的,溜了溜了(~ ̄▽ ̄)~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM