[GoF] 代理模式 Proxy
Proxy Sample Code @ pjchender github
代理模式的代理就很像是「經紀人」的概念,「使用者」不能直接或接觸到「對象」本身,而都需要透過中間的「經紀人(代理)。
代理模式的好處在於:
- 保護代理:透過代理,可以在實際向『對象』進行操作前,先進行初步的處理或過濾。
- 虛擬代理:在滿足特定條件下,代理才會實際向『對象』進行操作。
- 快取代理:如果傳進來的參數和先前的參數相同時,直接返回前一次儲存下來的結果。
透過代理模式,我們可以在不影響原本對象功能的情況下,增添其他的功能:
- 例如,
loadImg
方法只負責將圖片掛入 DOM 中,如果需要添加圖片載入中的 Preload 功能時,則可以額外撰寫proxyLoadImg
的方法,在proxyLoadImg
方法中,增加這個『顯示圖片載入中』 的功能。如此將不會動到原本loadImg
的功能(把圖片掛入 DOM 中),又可以添加『顯示圖片載入中』的功能。
讓代理的介面與本體的介面保持相同:
- 當我們讓代理的介面(interface)和對象本體的介面保持相同有一個很大的好處,從使用者的角度來說,使用者在呼叫代理或呼叫本體是沒有差別的,也就是使用者可以用相同的 API 來呼叫代理或呼叫本體,未來如果想要把代理移除並不需要做太多的修正,因為呼叫的 API 是相同的。
- 這麼做的好處在於,使用者可以無痛地在呼叫代理或呼叫本體間切換,端看他當時需要的結果是什麼。
常見的例子
proxy 常見的例子很多,像是 debounce
或 throttle
都可以被視為是一種虛擬代理的例子,呼叫 proxyMethod 和呼叫原本 method 的 interface 一樣,但在 proxy 中添加了一些行為。
React 中的 useMemo
則可以被視為一種快取代理。
範例程式碼
JavaScript Object Proxy
適合用在想要對物件的 CRUD 進行保護或攔截(intercept)時使用,例如,表單資料驗證、log 資料、formatting 輸出的資料等等(參考 JavaScript 代理 @ pjchender)。
虛擬代理
/**
* 透過建立 proxySynchronousFile,兩秒只會像 server 發送一次請求
* */
(function init() {
const synchronousFile = (id) => {
console.log(`開始同步檔案,檔案 id 為 ${id}`);
};
// 利用 closure 的技巧把 cache 保存起來
const proxySynchronousFile = (() => {
let cache = [];
let timer;
// 回傳一個很 synchronousFile 相同 interface 的 proxy function
return (id) => {
cache.push(id);
if (timer) {
return;
}
// 兩秒內只會發送一次請求
timer = setTimeout(() => {
synchronousFile([...new Set(cache)].join(','));
timer = null;
cache = [];
}, 2000);
};
})();
const checkboxes = document.getElementsByTagName('input');
[...checkboxes].forEach((checkbox) =>
checkbox.addEventListener('click', () => {
if (checkbox.checked) {
proxySynchronousFile(checkbox.id);
}
}),
);
})();
快取代理
/**
* 快取代理:如果傳進來的參數和先前的參數相同時,直接返回前一次儲存下來的結果。
* */
const multi = (...args: number[]): number => {
console.log('計算乘積: ', args.join(', '));
const result = args.reduce((acc, curr) => {
return acc * curr;
}, 1);
return result;
};
// 透過 closure 的方式來將資料保存在 cache 中
const multiProxy = (() => {
const cache = new Map<string, number>();
return (...args: number[]): number => {
const key = JSON.stringify(args);
// 如果 cache 已經算過,就直接拿出來
if (cache.has(key)) {
return cache.get(key) as number;
}
const result = multi(...args);
cache.set(key, result);
return result;
};
})();
export default multiProxy;