本篇文章中我們將要解決以下的問答。
- 什麼是代理器模式 ?
- 我們為什麼要使用它 ?
其中本篇文章還會介紹ES6
所提供的Proxy
使用方法。
什麼是代理器模式呢 ?
首先我們先來看一張下面這張圖,這張圖基本就說明了代理器模式的概念,無論如何,client
和Real 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
。
雖然上面的程式碼可以執行,但事實上有三個問題。
- 違反了
單一職責原則
,GetUser
裡面做了太多的事,會變的不好測試與修改。 - 上面的建構子有假設,他在實體化時,事實上需要做一些耗費資源的事情,這也表示我們的核心是
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
就是代表如果該實際物件被被設置欄位時,會先在這個proxy
的set
裡面先進行處理,其中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
}
結論
最後我們來簡單的為最上面問的兩個問題做些結論。
- 什麼是代理器模式 ?
- 我們為什麼要使用它 ?
- 代理器模式就是一個實際物件,只能透過代理器物件來連絡他。
- 而我們為什麼要使用他呢 ? 主要原因就是
為了保護實際物件
,不要讓他做太多的事,已經避免三不五十的都時用他,只有在真的會用到他時才使用。