設計模式 - 門面模式 (Facade Pattern)
Facade Pattern 中文又稱外觀模式或是門面模式,是一種將複雜的實作細節封裝,並對外提供簡單、方便和易懂的使用介面。
使用情境
假設今天有個需求:
- 透過打 API 取得 users 和 user posts 等資料
- 在打 API 時需要 log event,提供當出錯時 debug 用
一般來說,我們很有可能會這樣做:
import * as amplitude from '@amplitude/analytics-browser'
async function getUsers() {
try {
amplitude.logEvent('get_users_request')
const res = await fetch('https://example.com/api/users', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
amplitude.logEvent('get_users_request_success')
return res.json()
} catch (error) {
amplitude.logEvent('get_users_request_error', {
error_code: error.code,
error_message: error.message,
})
}
}
async function getUserPosts(id: string) {
try {
amplitude.logEvent('get_user_posts_request')
const res = await fetch(`https://example.com/api/posts?id=${id}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
amplitude.logEvent('get_user_posts_request_success')
return res.json()
} catch (error) {
amplitude.logEvent('get_user_posts_request_error', {
error_code: error.code,
error_message: error.message,
})
}
}
為什麼可能會有問題
程式碼可以跑,看起來也沒有問題!不過有發現嗎?在每個 getXXX
function 裡都需要引用不同的服務/功能,加上如果今天需求改變,發現想要改用 axios 取代 fetch,或是換了 logging service,全部的 functions 都需要做調整。如果 codebase 有一定規模的話,將會是一個浩大的工程。
用 Facade Pattern 隱藏實作細節
這時候 Facade Pattern 就派上用場了,我們可以把打 API、log event 的功能另外封裝,並用一個統一的 getFetchWithLogging
作為對外的介面使用。
import * as amplitude from '@amplitude/analytics-browser'
import axios from 'axios'
async function getUsers() {
return getFetchWithLogging(
'https://example.com/api/users',
{},
'get_users_request'
)
}
async function getUserPosts(userId: string) {
return getFetchWithLogging(
`https://example.com/api/posts`,
{ userId },
'get_user_posts_request'
)
}
async function getFetchWithLogging(
url: string,
params: Record<string, unknown> = {},
eventName: string
) {
try {
amplitude.logEvent(eventName)
const res = await axios.get(url, {
params,
})
amplitude.logEvent(`${eventName}_success`)
return res.data
} catch (error) {
amplitude.logEvent(`${eventName}_error`, {
error_code: error.code,
error_message: error.message,
})
}
}
使用 Facade Pattern 後,即便之後 axios API 有 break changes,或是我們想換成其他服務,只需要改動 getFetchWithLogging
,而不需要動到有使用 getFetchWithLogging
的地方。
後記
前端實務上,像是 UI components 也很適合用 Facade Pattern 封裝。公司可能會有固定使用的幾種 UI components,但基於各種考量,可能一開始會用 MUI 或是 Chakra UI 之類的 libraries;但後來慢慢發現選擇 library 的設計不一定符合公司需求,而有需要使用其他 library 或是自己實作,這時候如果一開始就先定義好 UI components 的 input/output,之後在轉換時就不需要在 codebase 做大幅度的改變,兒只需要改動 UI components 的實作細節就好。
雖然透過 facade 可以讓使用上更加容易和方便,不過 Facade Pattern 也是有像是可能會讓封裝的 facade 過於複雜、容易出錯、降低靈活性等隱憂,在使用上還是需要評估情境是否適合再使用。