Node 設計模式之代理器 ( Proxy )
nodejs design pattern
Lastmod: 2019-12-15

本篇文章中我們將要解決以下的問答。

  1. 什麼是代理器模式 ?
  2. 我們為什麼要使用它 ?

其中本篇文章還會介紹ES6所提供的Proxy使用方法。

什麼是代理器模式呢 ?

首先我們先來看一張下面這張圖,這張圖基本就說明了代理器模式的概念,無論如何,clientReal Object之間一定會由Proxy來進行溝通。

我們還可以用下面這句非常白話文的文字來表達代理器模式的精華。

我的時間很忙的,除非真的要用到我,不然請直接找我的代理人。

我們簡單來寫個範例來說明一下,代理器的實際上使用,首先我們先寫一個登入的程式。

Class UserService{
	construct () {
	
	}
	GetUser(name,password){
	    ......
	    return user;
	}
}

然後通常我們要使用的時後會執行下面程式碼。

const userService = new UserService();
const user = userService.GetUser("mark","123456789");

這樣看起來是沒什麼問題,東西是都還可以執行,然後我們來改寫成代理器的模式,我們會先建立一個UserServiceProxy,我們外面要使用UserService時,都只能透過這個Proxy進行溝通 (想找明星,只能想找他的代理人)。

注意 : 這只是其中一種寫法,代理器還有很多的方法可實現。

Class UserServiceProxy {
    construct(real){
      this.Real = real;
    }
    GetUser(name,passowrd){
       const real = new Real(); 
       real.getUser(name,password);
    }
}

const userServiceProxy = new UserServiceProxy(UserService);
const user = userServiceProxy.GetUser("mark","123456789");

為啥要用 Proxy 呢 ?

嗯 ? 那這樣有啥用處 ? 為要這樣寫 ? 上面的程式碼當然還看不出來代理器的優點,我們在將UserService裡的GetUser在增加幾行程式碼功能。

Class UserService{
    construct(){
       ... 為了使用getUserFromDB所需耗費的資源處理 (假設)
    
    }
	
	GetUser(name,password){
	    saveLog("get user");
	    
	    if(!name || !paaword){
	      throws error;
	    }
	    
		 if (chached(name,password)){
		   return user;
		 }
	    const user = getUserFromDB(name,password);
	    saveCache(user);
	    saveLog("get user success");
	    return user;
	}
}

GetUser這個程式碼,我們需要寫log,然後我們還要驗證使用者有沒有輸入帳號和密碼,接下來我們還要確認有沒有cache,如果有的話就直接從cache裡取出使用者,如果沒有就要使用帳號和密碼去取得該名使用者,最後還要在將它存放到cache中和再寫個log

雖然上面的程式碼可以執行,但事實上有三個問題。

  1. 違反了單一職責原則GetUser裡面做了太多的事,會變的不好測試與修改。
  2. 上面的建構子有假設,他在實體化時,事實上需要做一些耗費資源的事情,這也表示我們的核心是getUserFromDB除非確定要使用到他,不然請全部找他的代理人處理(Proxy)。

所以為了改善以上兩個問題我們將程式碼修改成下面這樣。

Class UserService{
    construct(){
       ...conect db.
       ...connect cached.
       ... 耗費很大的資源的東西
    
    }
	
	GetUser(name,password){
	    const user = getUserFromDB(name,password);
	    return user;
	}
}

Class UserServiceProxy{
    construct(real){
      this.Real = real;
    }
    GetUser(name,passowrd){
       saveLog("get User");
       if(!name || !paaword){
	      throws error;
	    }
	    
		 if (chached(name,password)){
		   return user;
		 }
       const real = new Real(); 
       real.GetUser(name,password);
       saveCache(user);
       saveLog("get user success");
    }
}

const userServiceProxy = new UserServiceProxy(UserService);
const user = userServiceProxy.GetUser("mark","123456789");

上面這種程式碼解決了上面說提到的兩個問題,首先我們將和不屬於getUser這個業務的東西完全的拉出來,他就變成單純的取得使用者,而致於第二個問題,實體化時會耗費資源這點也因為修改成上面這類型,我們只有在必要時才能需用真的用到Real Obj

上面這種寫法還有一個好處,假設我們UserService要修改成UserFBService的話我們只要確定UserFBService有實作GetUser,就幾乎可以不用修改程式碼直接進行使用。 不在這事實上是DI(Dependency Injection )的運用。

Class UserFBService{
    construct(){
       ...conect db.
       ...connect cached.
       ... 耗費很大的資源的東西
       ...connect fb...
    }
	
	GetUser(name,password){
	    const user = getUserFromDB(name,password);
	    saveCache(user);
	    return user;
	}
}

const userServiceProxy = new UserServiceProxy(UserFBService);
const user = userServiceProxy.GetUser("mark","123456789");

上面只是簡單的用程式碼來表達代理器可以做什麼事,但我們可以歸納出一句話,來說明為什麼要用代理器。

代理器就是為了保護實體而存在。

然後我們還可以將上面那句話,在簡單用三點功能來說明。

  • 攔截和監視外部進來的東西。
  • 降低實體物件的複雜性。
  • 在進行耗費資源的操作前,先進行管理。

ES6 的 Proxy

es6時,他有推出一個功能,那就是Proxy,它的核心功能事實上就和我們上面說的一樣,這邊在複習一次。

Proxy 就是為了保護實體而存在。

我們直接來看使用的方法。

資料設定的驗證

這邊我們將要來簡單的實作資料驗證的功能。

假設我們有一個物件obj,每當要為他設置欄位時,我們都要先驗證欄位該值不能為數字

let obj = {};
let input_data = new Proxy(obj,{
    set(target,key,value,proxy){
    	if(typeof value === 'number'){
    	   throw Error("不能是數值喔")
    	}
    	return Reflect.set(target,key,value,proxy);
    }
})

執行的結果如下,上面程式碼中,set就是代表如果該實際物件被被設置欄位時,會先在這個proxyset裡面先進行處理,其中target就是我們的obj實際物件,key就是我們要新增的欄位,以下範例來看就是title

input_data.title = 30
// Error !!!!!

input_data.title = "bababab"
// OK !!!!!

使用的 log 撰寫

假設我們有一個物件obj並且它有一個方法getData,然後我們每次在呼叫該方法時,會自動寫 log 。

const obj = {
  getData: () => {
    return {
      name: "mark",
      age: 20,
    }
  }
}

let proxy = new Proxy(obj,{
	get(target,key,proxy){
	  let value = target[key];
	  return (...arguments) => {
			console.log("log ~~~~ " + "fucntion :" + key)
	  		return Reflect.apply(value, target, arguments);
	  }
	}
})

執行結果如下程式碼。

proxy.getData();

// log ~~~~ fucntion :getData
{
   name: "mark",
   age: 20
}

結論

最後我們來簡單的為最上面問的兩個問題做些結論。

  1. 什麼是代理器模式 ?
  2. 我們為什麼要使用它 ?
  1. 代理器模式就是一個實際物件,只能透過代理器物件來連絡他。
  2. 而我們為什麼要使用他呢 ? 主要原因就是為了保護實際物件,不要讓他做太多的事,已經避免三不五十的都時用他,只有在真的會用到他時才使用。

參考資料

comments powered by Disqus