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

阿合奇ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:18982081108(備注:SSL證書合作)期待與您的合作!
下面按照「場景」-「設(shè)計模式定義」- 「代碼實現(xiàn)」- 「更多場景」-「總」的順序來總結(jié)一下,如有不當(dāng)之處,歡迎交流討論。
場景
(示例代碼來源于極客時間課程,React Hooks 核心原理與實戰(zhàn))
平常開發(fā)中一定遇到過這樣的場景:發(fā)起異步請求,loading 狀態(tài)顯示,獲取數(shù)據(jù)并顯示在界面上,如果遇到錯誤還會顯示錯誤狀態(tài)的相關(guān)展示。
為了方便運行,先寫一個 mock 數(shù)據(jù)的方法:
const list = {
page: 1,
per_page: 6,
total: 12,
total_pages: 2,
data: [
{
id: 1,
email: "george.bluth@reqres.in",
first_name: "windliang",
last_name: "windliang",
avatar: "https://reqres.in/img/faces/1-image.jpg"
},
{
id: 2,
email: "janet.weaver@reqres.in",
first_name: "Janet",
last_name: "Weaver",
avatar: "https://reqres.in/img/faces/2-image.jpg"
},
{
id: 3,
email: "emma.wong@reqres.in",
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() {
// 使用三個 state 分別保存用戶列表,loading 狀態(tài)和錯誤狀態(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();
// 請求成功后將用戶數(shù)據(jù)放入 state
setUsers(res.data);
} catch (err) {
// 請求失敗將錯誤狀態(tài)放入 state
setError(err);
}
setLoading(false);
};
return (
{error &&Failed: {String(error)}}
{users &&
users.length > 0 &&
users.map((user) => {
return- {user.first_name}
;
})}
);
}
效果就是下邊的樣子:
事實上,可能會有很多組件都需要這個過程,loading -> 展示數(shù)據(jù) -> loading消失、錯誤展示,每一個組件單獨維護這一套邏輯就太麻煩了,此時就可以用到模版模式了。
模版模式
看下 維基百科 給到的定義:
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]”
簡單來說,模版模式就是抽象父類提供一個骨架方法,里邊會調(diào)用一些抽象方法或者空方法,抽象方法/空方法由子類自行去實現(xiàn),可以看一下 UML 類圖。
image-20220210212704745
舉一個做飯的簡單例子,看一下代碼示例:
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("開吃!");
}
}
/*
準(zhǔn)備干凈鍋
拌雞蛋、切西紅柿
熱油,炒雞蛋,出鍋
少油,炒西紅柿,加鹽、加糖,加雞蛋炒
出鍋
準(zhǔn)備干凈鍋
切土豆片、腌肉
熱油,炒土豆片,出鍋
加油,蒜姜辣椒爆香,炒肉、加土豆炒
加生抽、加鹽、加老抽上色
出鍋
開吃!
*/Cook 類提供骨架方法 startCook ,編寫了做飯的主要流程,其他抽象方法 prepareIngredients 、 cooking下放給子類去實現(xiàn)自己獨有的邏輯。
讓我們用 js 來改寫一下:
const Cook = function () {};
Cook.prototype.prepare = function () {
console.log("準(zhǔn)備干凈鍋");
};
Cook.prototype.prepareIngredients = function () {
throw new Error("子類必須重寫 prepareIngredients 方法");
};
Cook.prototype.cooking = function () {
throw new Error("子類必須重寫 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("開吃!");上邊是 js 照貓畫虎的去按照 java的形式去實現(xiàn)模版方法,作為函數(shù)是一等公民的 js ,也許我們可以換一種方式。
js 的模版模式
模板模式是一個方法中定義一個算法骨架,可以讓子類在不改變算法整體結(jié)構(gòu)的情況下,重新定義算法中的某些步驟。
原始定義中通過抽象類繼承實現(xiàn),但由于js 并沒有抽象類,實現(xiàn)起來也有些繁瑣,也許我們可以通過組合的方式,將需要的方法以參數(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("開吃!");通過組合的方式,代碼會變得更加清爽簡單,不需要再定義 TomatoEgg 類和 Potato 類,只需要簡單的傳參。
但 js 實現(xiàn)的只能是帶引號的模版方法了,一方面我們并沒有通過繼承去實現(xiàn),另一方面 js 并沒有抽象類、抽象方法的功能,如果某些方法沒有實現(xiàn),并不能在代碼編寫階段發(fā)現(xiàn),到了運行階段才會收到Error。
代碼實現(xiàn)
回到開頭異步請求的例子,我們可以定義一個請求 Hook ,將 loaing 處理、數(shù)據(jù)返回處理這些步驟封裝起來,外界只需要傳遞請求的方法即可。
import { useState, useCallback } from "react";
export default (asyncFunction) => {
// 設(shè)置三個異步邏輯相關(guān)的 state
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// 定義一個 callback 用于執(zhí)行異步邏輯
const execute = useCallback(() => {
// 請求開始時,設(shè)置 loading 為 true,清除已有數(shù)據(jù)和 error 狀態(tài)
setLoading(true);
setData(null);
setError(null);
return asyncFunction()
.then((response) => {
// 請求成功時,將數(shù)據(jù)寫進(jìn) state,設(shè)置 loading 為 false
setData(response);
setLoading(false);
})
.catch((error) => {
// 請求失敗時,設(shè)置 loading 為 false,并設(shè)置錯誤狀態(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() {
// 通過 useAsync 這個函數(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é)也可以去運行下。
更多場景
「模版方法」在框架中會更常見,比如我們平常寫的 vue ,它的內(nèi)部定義了各個生命周期的執(zhí)行順序,然后對我們開放了生命周期的鉤子,可以執(zhí)行我們自己的操作。
「模版方法」如果再說的寬泛一點,ElementUI 的 dialog 也可以當(dāng)作模版方法。
title="提示"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose">
這是一段信息
el-dialog 實現(xiàn)了 Dialog 的基本樣式和行為,并且通過 slot 以供擴展,讓我們實現(xiàn)自己個性的東西。
總
雖然在 js 中我們并不能真正實現(xiàn)模版模式,但模版模式的作用我們還是實現(xiàn)了,踐行了「開放關(guān)閉原則」:
- 對擴展開放: 可以通過傳入不同的參數(shù),實現(xiàn)不同的應(yīng)用需求。
- 對修改關(guān)閉: 模版方法通過閉包的形式,內(nèi)部的屬性、方法外界并不能修改。
模版方法同樣提升了復(fù)用能力,我們可以把公共的部分提取到模版方法中,業(yè)務(wù)方就不需要自己再實現(xiàn)一次了。
名稱欄目:前端的設(shè)計模式系列-模版模式
標(biāo)題路徑:http://www.5511xx.com/article/cdcsepo.html


咨詢
建站咨詢
