跳至主要内容

[GoF] 代理模式 Proxy

Proxy Sample Code @ pjchender github

代理模式的代理就很像是「經紀人」的概念,「使用者」不能直接或接觸到「對象」本身,而都需要透過中間的「經紀人(代理)。

代理模式的好處在於:

  • 保護代理:透過代理,可以在實際向『對象』進行操作前,先進行初步的處理或過濾。
  • 虛擬代理:在滿足特定條件下,代理才會實際向『對象』進行操作。
  • 快取代理:如果傳進來的參數和先前的參數相同時,直接返回前一次儲存下來的結果。

透過代理模式,我們可以在不影響原本對象功能的情況下,增添其他的功能

  • 例如,loadImg 方法只負責將圖片掛入 DOM 中,如果需要添加圖片載入中的 Preload 功能時,則可以額外撰寫 proxyLoadImg 的方法,在 proxyLoadImg 方法中,增加這個『顯示圖片載入中』的功能。如此將不會動到原本 loadImg 的功能(把圖片掛入 DOM 中),又可以添加『顯示圖片載入中』的功能。

讓代理的介面與本體的介面保持相同

  • 當我們讓代理的介面(interface)和對象本體的介面保持相同有一個很大的好處,從使用者的角度來說,使用者在呼叫代理或呼叫本體是沒有差別的,也就是使用者可以用相同的 API 來呼叫代理或呼叫本體,未來如果想要把代理移除並不需要做太多的修正,因為呼叫的 API 是相同的。
  • 這麼做的好處在於,使用者可以無痛地在呼叫代理或呼叫本體間切換,端看他當時需要的結果是什麼。

常見的例子

proxy 常見的例子很多,像是 debouncethrottle 都可以被視為是一種虛擬代理的例子,呼叫 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;

參考資料

Giscus