代碼也寫了幾年了,設計模式處于看了忘,忘了看的狀态,最近對設計模式有了點感覺,索性就再學習總結下吧。
大部分講設計模式的文章都是使用的
Java
、
C++
這樣的以類為基礎的靜态類型語言,作為前端開發者,
js
這門基于原型的動态語言,函數成為了一等公民,在實作一些設計模式上稍顯不同,甚至簡單到不像使用了設計模式,有時候也會産生些困惑。
下面按照「場景」-「設計模式定義」- 「代碼實作」- 「更多場景」-「總」的順序來總結一下,如有不當之處,歡迎交流讨論。
場景
網絡請求中,我們一般使用
axios
庫,支援用
Promise
風格調用。
axios
.get("/api/user", {
params: {
ID: "123",
},
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
axios
.post(
"/api/user",
{
firstName: "wind",
lastName: "liang",
},
{
headers: { "Content-Type": "application/json" },
}
)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
複制
可以看到上邊的
get
和
post
傳參并不統一,使用起來會比較繁瑣,
post
還需要手動傳遞
headers
。
為了解決這些問題,我們可以通過外觀(門面)模式來解決。
外觀(門面)模式
看下 維基百科 的定義。
★The facade pattern (also spelled façade) is a software-design pattern commonly used in object-oriented programming. Analogous to a facade in architecture, a facade is an object that serves as a front-facing interface masking more complex underlying or structural code.
”
外觀模式相當于為一個相對複雜的接口或者結構提供一個上層接口供使用者使用,看一下
UML
類圖。
image-20220215084348154
舉一個簡單例子,比如開電腦是一個複雜的過程,我們可以封裝成一個函數來實作:
/* Complex parts */
class CPU {
public void freeze() { ... }
public void jump(long position) { ... }
public void execute() { ... }
}
class Memory {
public void load(long position, byte[] data) {
...
}
}
class HardDrive {
public byte[] read(long lba, int size) {
...
}
}
/* Façade */
class Computer {
public void startComputer() {
cpu.freeze();
memory.load(BOOT_ADDRESS, hardDrive.read(BOOT_SECTOR, SECTOR_SIZE));
cpu.jump(BOOT_ADDRESS);
cpu.execute();
}
}
/* Client */
class You {
public static void main(String[] args) {
Computer facade = new Computer();
facade.startComputer();
}
}
複制
改寫成
js
。
算了不改寫了,哈哈,直白點其實就是把幾個函數封裝到了一個函數來調用。
UML
類圖中外觀模式會和很多
class
互動,但在
js
中可能會很少遇到這種情況,通常是當參數比較複雜或者某個功能使用起來比較麻煩的時候我們就可以通過外觀模式進行簡化。
代碼實作
回到開頭
axios
的問題,我們可以對
axios
進行一層封裝。
// request.js
import axios from 'axios';
export const get = function (url, params) {
return axios.get(url, { params });
};
export const post = function (url, params) {
return axios.post(
url,
{ ...params },
{ headers: { "Content-Type": "application/json" } }
);
};
複制
然後引用
request.js
進行調用。
import { get, post } from "./request";
get("/api/user", {
ID: "123",
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
post("/api/user", {
firstName: "wind",
lastName: "liang",
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
複制
補充一句:上邊的封裝隻是為了示範外觀模式的使用,實際項目中封裝的會更加全面
通過門面模式除了簡化了我們的調用,還有一個好處就是将底層調用封裝了起來,未來如果底層需要變化,比如上邊的
axios
替換為
fetch
,我們隻需要去修改
request.js
即可,業務方無需感覺。
更多場景
外觀模式說的寬泛的話就是将複雜的調用包裝一層變的簡單些。
我們平時用到的
Vue
的
template
、
React
的
jsx
,也可以認為使用了外觀模式,他們都将底層
dom
建立封裝起來,使得我們編寫頁面會變得更加簡單。
易混設計模式
前邊講到的 代理模式、擴充卡模式、模版方法 結構上和外觀模式看起來都有些像,差別就在于他們的意圖不同:
- 擴充卡模式是為了解決兩個對象之間不比對的問題,而原對象又不适合直接修改,此時可以使用擴充卡模式進行一層轉換。
- 代理模式是為了增強原對象的功能,提供的接口不會改變。
- 模版模式是将不同功能組合在一起,隻提供架構,具體實作還需要調用者傳進來。
- 外觀模式是将比較複雜的調用進行一層封裝,提供一個新的接口供使用者使用。
總
外觀模式是一個比較自然的設計模式,某個功能感覺用起來太麻煩還頻繁,自然會想到去封裝一層再來使用。
外觀模式一個額外好處就是未來能夠更好的應對底層的變化。