来源:https://segmentfault.com/a/1190000023955401
作者:王亮hengg
浅谈 JavaScript 中策略模式的使用:
什么是设计模式
什么是策略模式
策略模式在 JavaScript 中的应用(使用策略模式封装百度AI识别调用)
策略模式在 Vue 组件封装中的应用(使用策略模式封装Select组件)


我们在写代码的时候,一定也遇到过许多类似的场景。 随着经验的增加,我们对于这些常见场景的处理越来越得心应手,甚至总结出了针对性的“套路”,下次遇到此类问题直接运用“套路”解决,省心又省力。 这些在软件开发过程中逐渐积累下来的“套路”就是设计模式。 ——《 设计模式沉思录 ,John Vlissides, 第一章 1.2节》


粗糙的表单校验
<form id='login-form' action="" method="post"><label for="account">手机号</label><input type="number" id="account" name="account"><label for="password">密码</label><input type="password" id="password" name="password"><button id='login'>登录</button></form><script>var loginForm = document.getElementById('login-form');loginForm.onsubmit = function (e) {e.preventDefault();var account = document.getElementById("account").value;var pwd = document.getElementById("password").value;if(account===null||account===''){alert('手机号不能为空');return false;}if(pwd===null||pwd===''){alert('密码不能为空');return false;}if (!/(^1[3|4|5|7|8][0-9]{9}$)/.test(account)) {alert('手机号格式错误');return false;}if(pwd.length<6){alert('密码不能小于六位');return false;}// ajax 发送请求}</script>
优秀的表单验证
var strategies = {isNonEmpty: function (value, errorMsg) {if (value === '' || value === null) {return errorMsg;}},isMobile: function (value, errorMsg) { // 手机号码格式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;}}};
var loginForm = document.getElementById('login-form');loginForm.onsubmit = function (e) {e.preventDefault();var accountIsMobile = strategies.isMobile(account,'手机号格式错误');var pwdMinLength = strategies.minLength(pwd,8,'密码不能小于8位');var errorMsg = accountIsMobile||pwdMinLength;if(errorMsg){alert(errorMsg);return false;}}
对比两种实现,我们可以看到:分离了校验逻辑的代码如果需要扩展校验类型,在策略组中新增定义即可使用;如果需要修改某个校验的实现,直接修改相应策略即可全局生效。对于开发和维护都有明显的效率提升。
扩展:史诗的表单校验


因为百度AI图像识别的接口类型不同,所需的参数格式也不尽相同。然而图像的压缩及上传、错误处理等部分是公用的。所以可以采用策略模式封装:
定义策略组
side
字段,自定义识别的
templateSign
字段,以及行驶证识别的接收参数为
poparamstData
。
/*** 策略组* IDCARD:身份证识别* CUSTOMIZED:自定义识别* VL:行驶证识别*/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 () { };/*** 读取图像,调用接口,获取识别结果** @param {*} type 待识别文件类型* @param {*} base64 待识别文件 BASE64码* @param {*} callBack 识别结果回调*/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 层的统一的错误码if (data.error_code !== undefined && data.error_code !== 0) {var errorData = {error: 1,title: '错误 ' + data.error_code,content: 'error message'};callBack(errorData);} else {callBack(data);}}});};};
调用方式
var imageReader = new ImageReader();imageReader.getOcrResult('IDCARD', this.result.toString(), callback);


Context
<template><el-select v-model="selectedValue" placeholder="请选择" @change="optionChanged" size="mini" clearable><el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id"></el-option></el-select></template>
data() {return {selectedValue: undefined,options: [],action: "",};},props: {// 暴露给外部的 select-typeselectType: {type: String},},created() {// 获取 optionsthis.valuation();},methods: {optionChanged() {this.$emit(this.action, this.selectedValue);},setOptions(option) {this.$store.dispatch(this.action, option);},valuation() {// 获取 options 数据}},
<MySelect selectType="product"/>
strategies
let strategies = {source: {action: "sourceOption",getOptions: function() {// 拉取 options}},product: {action: "productOption",getOptions: function() {// 拉取 options}},...}
异步
// 策略组修改source: {action: "sourceOption",getOptions: async function() {// await 拉取 options}},// 组件修改methods: {...async valuation() {...}}
继续优化
最开始的思路是高阶组件,即定义一个包装后的select模板,通过高阶组件的方式扩展其数据源与action(变化的部分)然而这个思路不是那么的vue(主要是slots不太好处理) 于是考虑策略模式改写该组件


通过以上两个例子,我们可以看到:
-
策略模式符合开放-封闭原则 -
如果代码里需要写大量的 if-else语句,那么考虑使用策略模式 -
如果多个组件(类)之间的区别仅在于它们的行为,考虑采用策略模式




本文分享自微信公众号 - CC前端手记(codenotescc)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
来源:oschina
链接:https://my.oschina.net/u/4475588/blog/4657206
