新聞中心
大部分講設(shè)計(jì)模式的文章都是使用的 Java、C++ 這樣的以類(lèi)為基礎(chǔ)的靜態(tài)類(lèi)型語(yǔ)言,作為前端開(kāi)發(fā)者,js 這門(mén)基于原型的動(dòng)態(tài)語(yǔ)言,函數(shù)成為了一等公民,在實(shí)現(xiàn)一些設(shè)計(jì)模式上稍顯不同,甚至簡(jiǎn)單到不像使用了設(shè)計(jì)模式,有時(shí)候也會(huì)產(chǎn)生些困惑。

阿合奇ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場(chǎng)景,ssl證書(shū)未來(lái)市場(chǎng)廣闊!成為創(chuàng)新互聯(lián)公司的ssl證書(shū)銷(xiāo)售渠道,可以享受市場(chǎng)價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:18982081108(備注:SSL證書(shū)合作)期待與您的合作!
下面按照「場(chǎng)景」-「設(shè)計(jì)模式定義」- 「代碼實(shí)現(xiàn)」- 「更多場(chǎng)景」-「總」的順序來(lái)總結(jié)一下,如有不當(dāng)之處,歡迎交流討論。
場(chǎng)景
(示例代碼來(lái)源于極客時(shí)間課程,React Hooks 核心原理與實(shí)戰(zhàn))
平常開(kāi)發(fā)中一定遇到過(guò)這樣的場(chǎng)景:發(fā)起異步請(qǐng)求,loading 狀態(tài)顯示,獲取數(shù)據(jù)并顯示在界面上,如果遇到錯(cuò)誤還會(huì)顯示錯(cuò)誤狀態(tài)的相關(guān)展示。
為了方便運(yùn)行,先寫(xiě)一個(gè) mock 數(shù)據(jù)的方法:
const list = {
page: 1,
per_page: 6,
total: 12,
total_pages: 2,
data: [
{
id: 1,
email: "[email protected]",
first_name: "windliang",
last_name: "windliang",
avatar: "https://reqres.in/img/faces/1-image.jpg"
},
{
id: 2,
email: "[email protected]",
first_name: "Janet",
last_name: "Weaver",
avatar: "https://reqres.in/img/faces/2-image.jpg"
},
{
id: 3,
email: "[email protected]",
first_name: "Emma",
last_name: "Wong",
avatar: "https://reqres.in/img/faces/3-image.jpg"
}
]
};
export const getDataMock = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(list);
}, 2000);
});然后是列表組件:
import React from "react";
import { getDataMock } from "./mock";
export default function UserList() {
// 使用三個(gè) state 分別保存用戶列表,loading 狀態(tài)和錯(cuò)誤狀態(tài)
const [users, setUsers] = React.useState([]);
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState(null);
// 定義獲取用戶的回調(diào)函數(shù)
const fetchUsers = async () => {
setLoading(true);
try {
const res = await getDataMock();
// 請(qǐng)求成功后將用戶數(shù)據(jù)放入 state
setUsers(res.data);
} catch (err) {
// 請(qǐng)求失敗將錯(cuò)誤狀態(tài)放入 state
setError(err);
}
setLoading(false);
};
return (
{error &&Failed: {String(error)}}
{users &&
users.length > 0 &&
users.map((user) => {
return- {user.first_name}
;
})}
);
}
效果就是下邊的樣子:
事實(shí)上,可能會(huì)有很多組件都需要這個(gè)過(guò)程,loading -> 展示數(shù)據(jù) -> loading消失、錯(cuò)誤展示,每一個(gè)組件單獨(dú)維護(hù)這一套邏輯就太麻煩了,此時(shí)就可以用到模版模式了。
模版模式
看下 維基百科 給到的定義:
The template method is a method in a superclass, usually an abstract superclass, and defines the skeleton of an operation in terms of a number of high-level steps. These steps are themselves implemented by additional helper methods in the same class as the template method.”
The helper methods may be either abstract methods, in which case subclasses are required to provide concrete implementations, or hook methods, which have empty bodies in the superclass. Subclasses can (but are not required to) customize the operation by overriding the hook methods. The intent of the template method is to define the overall structure of the operation, while allowing subclasses to refine, or redefine, certain steps.[2]”
簡(jiǎn)單來(lái)說(shuō),模版模式就是抽象父類(lèi)提供一個(gè)骨架方法,里邊會(huì)調(diào)用一些抽象方法或者空方法,抽象方法/空方法由子類(lèi)自行去實(shí)現(xiàn),可以看一下 UML 類(lèi)圖。
image-20220210212704745
舉一個(gè)做飯的簡(jiǎn)單例子,看一下代碼示例:
abstract class Cook {
public abstract void prepareIngredients();
public abstract void cooking();
public void prepare() {
System.out.println("準(zhǔn)備干凈鍋");
}
/* A template method : */
public final void startCook() {
prepare();
prepareIngredients();
cooking();
}
}
class TomatoEgg extends Cook {
@Override
public void prepareIngredients() {
System.out.println("拌雞蛋、切西紅柿");
}
@Override
public void cooking() {
System.out.println("熱油,炒雞蛋,出鍋");
System.out.println("少油,炒西紅柿,加鹽、加糖,加雞蛋炒");
System.out.println("出鍋");
}
}
class Potato extends Cook {
@Override
public void prepareIngredients() {
System.out.println("切土豆片、腌肉");
}
@Override
public void cooking() {
System.out.println("熱油,炒土豆片,出鍋");
System.out.println("加油,蒜姜辣椒爆香,炒肉、加土豆炒");
System.out.println("加生抽、加鹽、加老抽上色");
System.out.println("出鍋");
}
}
public class Main {
public static void main(String[] args) {
Cook tomatoEgg = new TomatoEgg();
tomatoEgg.startCook();
Cook potato = new Potato();
potato.startCook();
System.out.println("開(kāi)吃!");
}
}
/*
準(zhǔn)備干凈鍋
拌雞蛋、切西紅柿
熱油,炒雞蛋,出鍋
少油,炒西紅柿,加鹽、加糖,加雞蛋炒
出鍋
準(zhǔn)備干凈鍋
切土豆片、腌肉
熱油,炒土豆片,出鍋
加油,蒜姜辣椒爆香,炒肉、加土豆炒
加生抽、加鹽、加老抽上色
出鍋
開(kāi)吃!
*/Cook 類(lèi)提供骨架方法 startCook ,編寫(xiě)了做飯的主要流程,其他抽象方法 prepareIngredients 、 cooking下放給子類(lèi)去實(shí)現(xiàn)自己獨(dú)有的邏輯。
讓我們用 js 來(lái)改寫(xiě)一下:
const Cook = function () {};
Cook.prototype.prepare = function () {
console.log("準(zhǔn)備干凈鍋");
};
Cook.prototype.prepareIngredients = function () {
throw new Error("子類(lèi)必須重寫(xiě) prepareIngredients 方法");
};
Cook.prototype.cooking = function () {
throw new Error("子類(lèi)必須重寫(xiě) cooking 方法");
};
Cook.prototype.startCook = function () {
this.prepare();
this.prepareIngredients();
this.cooking();
};
const TomatoEgg = function () {};
TomatoEgg.prototype = new Cook();
TomatoEgg.prototype.prepareIngredients = function () {
console.log("拌雞蛋、切西紅柿");
};
TomatoEgg.prototype.cooking = function () {
console.log("熱油,炒雞蛋,出鍋");
console.log("少油,炒西紅柿,加鹽、加糖,加雞蛋炒");
console.log("出鍋");
};
const Potato = function () {};
Potato.prototype = new Cook();
Potato.prototype.prepareIngredients = function () {
console.log("切土豆片、腌肉");
};
Potato.prototype.cooking = function () {
console.log("熱油,炒土豆片,出鍋");
console.log("加油,蒜姜辣椒爆香,炒肉、加土豆炒");
console.log("加生抽、加鹽、加老抽上色");
console.log("出鍋");
};
const tomatoEgg = new TomatoEgg();
tomatoEgg.startCook();
const potato = new Potato();
potato.startCook();
console.log("開(kāi)吃!");上邊是 js 照貓畫(huà)虎的去按照 java的形式去實(shí)現(xiàn)模版方法,作為函數(shù)是一等公民的 js ,也許我們可以換一種方式。
js 的模版模式
模板模式是一個(gè)方法中定義一個(gè)算法骨架,可以讓子類(lèi)在不改變算法整體結(jié)構(gòu)的情況下,重新定義算法中的某些步驟。
原始定義中通過(guò)抽象類(lèi)繼承實(shí)現(xiàn),但由于js 并沒(méi)有抽象類(lèi),實(shí)現(xiàn)起來(lái)也有些繁瑣,也許我們可以通過(guò)組合的方式,將需要的方法以參數(shù)的形式傳給算法骨架。
const Cook = function ({ prepareIngredients, cooking }) {
const prepare = function () {
console.log("準(zhǔn)備干凈鍋");
};
const startCook = function () {
prepare();
prepareIngredients();
cooking();
};
return {
startCook,
};
};
const tomatoEgg = Cook({
prepareIngredients() {
console.log("拌雞蛋、切西紅柿");
},
cooking() {
console.log("熱油,炒雞蛋,出鍋");
console.log("少油,炒西紅柿,加鹽、加糖,加雞蛋炒");
console.log("出鍋");
},
});
tomatoEgg.startCook();
const potato = Cook({
prepareIngredients() {
console.log("切土豆片、腌肉");
},
cooking() {
console.log("熱油,炒土豆片,出鍋");
console.log("加油,蒜姜辣椒爆香,炒肉、加土豆炒");
console.log("加生抽、加鹽、加老抽上色");
console.log("出鍋");
},
});
potato.startCook();
console.log("開(kāi)吃!");通過(guò)組合的方式,代碼會(huì)變得更加清爽簡(jiǎn)單,不需要再定義 TomatoEgg 類(lèi)和 Potato 類(lèi),只需要簡(jiǎn)單的傳參。
但 js 實(shí)現(xiàn)的只能是帶引號(hào)的模版方法了,一方面我們并沒(méi)有通過(guò)繼承去實(shí)現(xiàn),另一方面 js 并沒(méi)有抽象類(lèi)、抽象方法的功能,如果某些方法沒(méi)有實(shí)現(xiàn),并不能在代碼編寫(xiě)階段發(fā)現(xiàn),到了運(yùn)行階段才會(huì)收到Error。
代碼實(shí)現(xiàn)
回到開(kāi)頭異步請(qǐng)求的例子,我們可以定義一個(gè)請(qǐng)求 Hook ,將 loaing 處理、數(shù)據(jù)返回處理這些步驟封裝起來(lái),外界只需要傳遞請(qǐng)求的方法即可。
import { useState, useCallback } from "react";
export default (asyncFunction) => {
// 設(shè)置三個(gè)異步邏輯相關(guān)的 state
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// 定義一個(gè) callback 用于執(zhí)行異步邏輯
const execute = useCallback(() => {
// 請(qǐng)求開(kāi)始時(shí),設(shè)置 loading 為 true,清除已有數(shù)據(jù)和 error 狀態(tài)
setLoading(true);
setData(null);
setError(null);
return asyncFunction()
.then((response) => {
// 請(qǐng)求成功時(shí),將數(shù)據(jù)寫(xiě)進(jìn) state,設(shè)置 loading 為 false
setData(response);
setLoading(false);
})
.catch((error) => {
// 請(qǐng)求失敗時(shí),設(shè)置 loading 為 false,并設(shè)置錯(cuò)誤狀態(tài)
setError(error);
setLoading(false);
});
}, [asyncFunction]);
return { execute, loading, data, error };
};業(yè)務(wù)調(diào)用的地方使用上邊的 Hook 即可。
import React from "react";
import useAsync from "./useAsync";
import { getDataMock } from "./mock";
export default function UserList() {
// 通過(guò) useAsync 這個(gè)函數(shù),只需要提供異步邏輯的實(shí)現(xiàn)
const { execute: fetchUsers, data: users, loading, error } = useAsync(
async () => {
const res = await getDataMock();
return res.data;
}
);
return (
{error &&Failed: {String(error)}}
{users &&
users.length > 0 &&
users.map((user) => {
return- {user.first_name}
;
})}
);
}
完整代碼放到 Sandxox 上了,感興趣的同學(xué)也可以去運(yùn)行下。
更多場(chǎng)景
「模版方法」在框架中會(huì)更常見(jiàn),比如我們平常寫(xiě)的 vue ,它的內(nèi)部定義了各個(gè)生命周期的執(zhí)行順序,然后對(duì)我們開(kāi)放了生命周期的鉤子,可以執(zhí)行我們自己的操作。
「模版方法」如果再說(shuō)的寬泛一點(diǎn),ElementUI 的 dialog 也可以當(dāng)作模版方法。
title="提示"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose">
這是一段信息
el-dialog 實(shí)現(xiàn)了 Dialog 的基本樣式和行為,并且通過(guò) slot 以供擴(kuò)展,讓我們實(shí)現(xiàn)自己個(gè)性的東西。
總
雖然在 js 中我們并不能真正實(shí)現(xiàn)模版模式,但模版模式的作用我們還是實(shí)現(xiàn)了,踐行了「開(kāi)放關(guān)閉原則」:
- 對(duì)擴(kuò)展開(kāi)放: 可以通過(guò)傳入不同的參數(shù),實(shí)現(xiàn)不同的應(yīng)用需求。
- 對(duì)修改關(guān)閉: 模版方法通過(guò)閉包的形式,內(nèi)部的屬性、方法外界并不能修改。
模版方法同樣提升了復(fù)用能力,我們可以把公共的部分提取到模版方法中,業(yè)務(wù)方就不需要自己再實(shí)現(xiàn)一次了。
文章名稱:前端的設(shè)計(jì)模式系列-模版模式
網(wǎng)站地址:http://m.fisionsoft.com.cn/article/cdcsepo.html


咨詢
建站咨詢
