新聞中心
淺談 JavaScript 中策略模式的使用:

作為一家“創(chuàng)意+整合+營(yíng)銷”的成都網(wǎng)站建設(shè)機(jī)構(gòu),我們?cè)跇I(yè)內(nèi)良好的客戶口碑。創(chuàng)新互聯(lián)提供從前期的網(wǎng)站品牌分析策劃、網(wǎng)站設(shè)計(jì)、網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、創(chuàng)意表現(xiàn)、網(wǎng)頁(yè)制作、系統(tǒng)開發(fā)以及后續(xù)網(wǎng)站營(yíng)銷運(yùn)營(yíng)等一系列服務(wù),幫助企業(yè)打造創(chuàng)新的互聯(lián)網(wǎng)品牌經(jīng)營(yíng)模式與有效的網(wǎng)絡(luò)營(yíng)銷方法,創(chuàng)造更大的價(jià)值。
- 什么是設(shè)計(jì)模式
- 什么是策略模式
- 策略模式在 JavaScript 中的應(yīng)用(使用策略模式封裝百度AI識(shí)別調(diào)用)
- 策略模式在 Vue 組件封裝中的應(yīng)用(使用策略模式封裝Select組件)
什么是設(shè)計(jì)模式
設(shè)想有一個(gè)電子愛好者,雖然他沒(méi)有經(jīng)過(guò)正規(guī)的培訓(xùn),但是卻日積月累地設(shè)計(jì)并制造出了許多有用的電子設(shè)備:業(yè)余無(wú)線電、蓋革計(jì)數(shù)器、報(bào)警器等。有一天這個(gè)愛好者決定重新回到學(xué)校去攻讀電子學(xué)學(xué)位,來(lái)讓自己的才能得到正式的認(rèn)可。隨著課程的展開,這個(gè)愛好者突然發(fā)現(xiàn)課程內(nèi)容都似曾相識(shí)。似曾相識(shí)的不是術(shù)語(yǔ)或表述的方式,而是背后的概念。這個(gè)愛好者不斷學(xué)到一些名稱和原理,雖然這些名稱和原理原來(lái)他并不知道,但事實(shí)上他多年以來(lái)一直都在使用。整個(gè)過(guò)程只不過(guò)是一個(gè)接一個(gè)的頓悟。
設(shè)計(jì)模式沉思錄 ,John Vlissides, 第一章 1.2節(jié)
我們?cè)趯懘a的時(shí)候,一定也遇到過(guò)許多類似的場(chǎng)景。隨著經(jīng)驗(yàn)的增加,我們對(duì)于這些常見場(chǎng)景的處理越來(lái)越得心應(yīng)手,甚至總結(jié)出了針對(duì)性的“套路”,下次遇到此類問(wèn)題直接運(yùn)用“套路”解決,省心又省力。這些在軟件開發(fā)過(guò)程中逐漸積累下來(lái)的“套路”就是設(shè)計(jì)模式。
設(shè)計(jì)模式的目標(biāo)之一就是提高代碼的可復(fù)用性、可擴(kuò)展性和可維護(hù)性。正因如此,雖然有時(shí)候我們不知道某個(gè)設(shè)計(jì)模式,但是看了相關(guān)書籍或文章后會(huì)有一種“啊,原來(lái)這就是設(shè)計(jì)模式”的恍然大明白。
如果你看完這篇文章后也有此感覺(jué),那么恭喜你,你已經(jīng)在高效程序員的道路上一路狂奔了。
什么是策略模式
策略模式是一種簡(jiǎn)單卻常用的設(shè)計(jì)模式,它的應(yīng)用場(chǎng)景非常廣泛。我們先了解下策略模式的概念,再通過(guò)代碼示例來(lái)更清晰的認(rèn)識(shí)它。
策略模式由兩部分構(gòu)成:一部分是封裝不同策略的策略組,另一部分是 Context。通過(guò)組合和委托來(lái)讓 Context 擁有執(zhí)行策略的能力,從而實(shí)現(xiàn)可復(fù)用、可擴(kuò)展和可維護(hù),并且避免大量復(fù)制粘貼的工作。
策略模式的典型應(yīng)用場(chǎng)景是表單校驗(yàn)中,對(duì)于校驗(yàn)規(guī)則的封裝。接下來(lái)我們就通過(guò)一個(gè)簡(jiǎn)單的例子具體了解一下:
粗糙的表單校驗(yàn)
一個(gè)常見的登錄表單代碼如下:
以上代碼,雖然功能沒(méi)問(wèn)題,但是缺點(diǎn)也很明顯:
代碼里遍地都是 if 語(yǔ)句,并且它們?nèi)狈椥裕好啃略鲆环N、或者修改原有校驗(yàn)規(guī)則,我們都必須去改loginForm.onsubmit內(nèi)部的代碼。另外邏輯的復(fù)用性也很差:如果有其它表單也是用同樣的規(guī)則,這段代碼并不能復(fù)用,只能復(fù)制。當(dāng)校驗(yàn)規(guī)則發(fā)生變化時(shí),比如上文的正則校驗(yàn)并不能匹配虛擬運(yùn)營(yíng)商14/17號(hào)段,我們就需要手動(dòng)同步多處代碼變更(Ctrl+C/Ctrl+V)。
優(yōu)秀的表單驗(yàn)證
接下來(lái)我們通過(guò)策略模式的思路改寫一下上段代碼,首先抽離并封裝校驗(yàn)邏輯為策略組:
- var strategies = {
- isNonEmpty: function (value, errorMsg) {
- if (value === '' || value === null) {
- return errorMsg;
- }
- },
- isMobile: function (value, errorMsg) { // 手機(jī)號(hào)碼格式
- if (!/(^1[3|4|5|7|8][0-9]{9}$)/.test(value)) {
- return errorMsg;
- }
- },
- minLength: function (value, length, errorMsg) {
- if (value.length < length) {
- return errorMsg;
- }
- }
- };
接下來(lái)修改 Context:
- var loginForm = document.getElementById('login-form');
- loginForm.onsubmit = function (e) {
- e.preventDefault();
- var accountIsMobile = strategies.isMobile(account,'手機(jī)號(hào)格式錯(cuò)誤');
- var pwdMinLength = strategies.minLength(pwd,8,'密碼不能小于8位');
- var errorMsg = accountIsMobile||pwdMinLength;
- if(errorMsg){
- alert(errorMsg);
- return false;
- }
- }
對(duì)比兩種實(shí)現(xiàn),我們可以看到:分離了校驗(yàn)邏輯的代碼如果需要擴(kuò)展校驗(yàn)類型,在策略組中新增定義即可使用;如果需要修改某個(gè)校驗(yàn)的實(shí)現(xiàn),直接修改相應(yīng)策略即可全局生效。對(duì)于開發(fā)和維護(hù)都有明顯的效率提升。
擴(kuò)展:史詩(shī)的表單校驗(yàn)
有興趣的朋友可以了解下 async-validator ,element-ui 和 antd 的表單校驗(yàn)都是基于 async-validator 封裝的,可以說(shuō)是史詩(shī)級(jí)別的表單校驗(yàn)了
通過(guò)表單校驗(yàn)的對(duì)比,相信大家都對(duì)策略模式有所了解,那么接下來(lái)通過(guò)兩個(gè)例子具體了解下 JavaScript 中策略模式的應(yīng)用:
使用策略模式調(diào)用百度AI圖像識(shí)別
因?yàn)榘俣華I圖像識(shí)別的接口類型不同,所需的參數(shù)格式也不盡相同。然而圖像的壓縮及上傳、錯(cuò)誤處理等部分是公用的。所以可以采用策略模式封裝:
定義策略組
通過(guò)定義策略組來(lái)封裝不同的接口及其參數(shù):比如身份證識(shí)別接口的side字段,自定義識(shí)別的templateSign字段,以及行駛證識(shí)別的接收參數(shù)為poparamstData。
- /**
- * 策略組
- * IDCARD:身份證識(shí)別
- * CUSTOMIZED:自定義識(shí)別
- * VL:行駛證識(shí)別
- */
- var strategies = {
- IDCARD: function (base64) {
- return {
- path: 'idcard',
- param: {
- 'side': 'front',
- 'base64': base64
- }
- };
- },
- CUSTOMIZED: function (base64) {
- return {
- path: 'customized',
- param: {
- 'templateSign': '52cc2d402155xxxx',
- 'base64': base64
- }
- };
- },
- VL: function (base64) {
- return {
- path: 'vehicled',
- poparamstData: {
- 'base64': base64
- }
- };
- },
- };
定義 Context
- var ImageReader = function () { };
- /**
- * 讀取圖像,調(diào)用接口,獲取識(shí)別結(jié)果
- *
- * @param {*} type 待識(shí)別文件類型
- * @param {*} base64 待識(shí)別文件 BASE64碼
- * @param {*} callBack 識(shí)別結(jié)果回調(diào)
- */
- ImageReader.prototype.getOcrResult = function (type, base64, callBack) {
- let fileSize = (base64.length / (1024 * 1024)).toFixed(2);
- let compressedBase64 = '';
- let image = new Image();
- image.src = base64;
- image.onload = function () {
- /**
- * 圖片壓縮處理及異常處理,代碼略
- */
- let postData = strategies[type](compressedBase64);
- ajax(
- host + postData.path, {
- data: postData.param,
- type: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded'
- },
- success: function (res) {
- var data = JSON.parse(res);
- // 暴露給 UI 層的統(tǒng)一的錯(cuò)誤碼
- if (data.error_code !== undefined && data.error_code !== 0) {
- var errorData = {
- error: 1,
- title: '錯(cuò)誤 ' + data.error_code,
- content: 'error message'
- };
- callBack(errorData);
- } else {
- callBack(data);
- }
- }
- });
- };
- };
調(diào)用方式
- var imageReader = new ImageReader();
- imageReader.getOcrResult('IDCARD', this.result.toString(), callback);
使用策略模式封裝 Vue Select 組件
某項(xiàng)目中多處用到了 element-ui 的 select 組件,其內(nèi)在邏輯類似,都是初始化時(shí)獲取下拉列表的數(shù)據(jù)源,然后在選中某一項(xiàng)時(shí) dispatch 不同的 action。遂考慮使用策略模式封裝。
Context
在本例中,組件向外部暴露一個(gè) prop,調(diào)用方指定該 prop 從而加載不同的策略。那么定義 Context 如下:
- data() {
- return {
- selectedValue: undefined,
- options: [],
- action: "",
- };
- },
- props: {
- // 暴露給外部的 select-type
- selectType: {
- type: String
- },
- },
- created() {
- // 獲取 options
- this.valuation();
- },
- methods: {
- optionChanged() {
- this.$emit(this.action, this.selectedValue);
- },
- setOptions(option) {
- this.$store.dispatch(this.action, option);
- },
- valuation() {
- // 獲取 options 數(shù)據(jù)
- }
- },
外部通過(guò)如下方式調(diào)用組件:
strategies
然后定義策略組:
- let strategies = {
- source: {
- action: "sourceOption",
- getOptions: function() {
- // 拉取 options
- }
- },
- product: {
- action: "productOption",
- getOptions: function() {
- // 拉取 options
- }
- },
- ...
- }
異步
至此該組件的基本結(jié)構(gòu)已經(jīng)清晰,但還存在一個(gè)問(wèn)題:組件加載時(shí)是異步拉取的 options, 而頁(yè)面初始化的時(shí)候很可能 options 還沒(méi)有返回,導(dǎo)致 select 的 options 仍為空。所以此處應(yīng)該修改代碼,同步獲取 options:
- // 策略組修改
- source: {
- action: "sourceOption",
- getOptions: async function() {
- // await 拉取 options
- }
- },
- // 組件修改
- methods: {
- ...
- async valuation() {
- ...
- }
- }
繼續(xù)優(yōu)化
但我們不是每次加載組件都需要拉取 options,如果這些 options 在其他組件或者頁(yè)面也被使用到,那么可以考慮將其存入 vuex 中。
最開始的思路是高階組件,即定義一個(gè)包裝后的select模板,通過(guò)高階組件的方式擴(kuò)展其數(shù)據(jù)源與action(變化的部分)然而這個(gè)思路不是那么的vue(主要是slots不太好處理) 于是考慮策略模式改寫該組件
總結(jié)
通過(guò)以上兩個(gè)例子,我們可以看到:
- 策略模式符合開放-封閉原則
- 如果代碼里需要寫大量的if-else語(yǔ)句,那么考慮使用策略模式
- 如果多個(gè)組件(類)之間的區(qū)別僅在于它們的行為,考慮采用策略模式
網(wǎng)站欄目:5分鐘即可掌握的前端高效利器:JavaScript策略模式
文章來(lái)源:http://www.dlmjj.cn/article/dppihdj.html


咨詢
建站咨詢
