On the use of strategy patterns in JavaScript:
- What are design patterns
- What are strategic patterns
- Application of Policy Pattern in JavaScript (using policy pattern to encapsulate Baidu AI recognition calls)
- Application of Policy Pattern in Vue Component Encapsulation (Using Policy Pattern to encapsulate Select Component)
What are design patterns
Consider an electronics enthusiast who, despite his lack of formal training, has accumulated over time to design and build a number of useful electronic devices: ham radios, Geiger counters, alarms, etc. One day the enthusiast decided to go back to school for a degree in electronics to get formal recognition for his talents. As the course unfolded, the hobbyist suddenly realized that everything was familiar. What is familiar is not the terminology or the way it is expressed, but the concept behind it. The hobbyist kept learning names and principles which he did not know, but which he had been using for years. The whole process was just one Epiphany after another.
Meditations on Design Patterns, John Vlissides, Chapter 1, section 1.2
We must have encountered many similar scenarios when writing code. With the increase of experience, we are more and more comfortable in dealing with these common scenes, and even summarize the targeted “routines”. Next time we encounter such problems, we can directly use the “routines” to solve them, saving both worry and effort. These “tropes” that accumulate over the course of software development are design patterns.
One of the goals of design patterns is to improve code reusability, extensibility, and maintainability. Because of this, sometimes we don’t know about a design pattern, but when we read a book or article about it, it’s like, “Oh, that’s a design pattern!”
If you feel that way after reading this article, you’re well on your way to becoming a productive programmer.
What are strategic patterns
Strategy pattern is a simple but commonly used design pattern, and its application scenarios are very wide. Let’s take a look at the concept of policy patterns and use code examples to make it clearer.
A policy pattern consists of two parts: one is a policy group that encapsulates different policies, and the other is a Context. Make the Context reusable, extensible, and maintainable by combining and delegating the ability to enforce policies and avoid a lot of copy-and-paste work.
A typical application of policy pattern is encapsulation of validation rules in form validation. Let’s take a look at a simple example:
Crude form validation
A common login form code is as follows:
<form id='login-form' action="" method="post">
<label for="account">Mobile phone no.</label>
<input type="number" id="account" name="account">
<label for="password">password</label>
<input type="password" id="password" name="password">
<button id='login'>The 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('Mobile phone number cannot be empty');return false;
}
if(pwd===null||pwd===' '){
alert('Password cannot be empty');return false;
}
if (!/ (a ^ 1 [3 4 5 7 | | | | 8] [0-9] {9} $) /.test(account)) {
alert('Mobile phone number format error');return false;
}
if(pwd.length<6){
alert('Password must be no less than six characters');return false;
}
// Ajax sends the request
}
</script>
Copy the code
The above code, while functional, but also obvious shortcomings:
The code is littered with if statements, and they’re inelastic: Every time we add one or change the validation rule, we have to change the code inside loginForm. Onsubmit. The logic is also poorly reusable: if there are other forms that use the same rules, the code cannot be reused, only copied. When the verification rule changes, such as the previous regular verification does not match the virtual carrier 14/17 segment, we need to manually synchronize multiple code changes (Ctrl+C/Ctrl+V).
Good form validation
Next, we will rewrite the previous code by using the policy pattern thinking. First, we will extract and encapsulate the verification logic as the policy group:
var strategies = {
isNonEmpty: function (value, errorMsg) {
if (value === ' ' || value === null) {
returnerrorMsg; }},isMobile: function (value, errorMsg) { // Mobile phone number format
if (!/ (a ^ 1 [3 4 5 7 | | | | 8] [0-9] {9} $) /.test(value)) {
returnerrorMsg; }},minLength: function (value, length, errorMsg) {
if (value.length < length) {
returnerrorMsg; }}};Copy the code
Next modify the Context:
var loginForm = document.getElementById('login-form');
loginForm.onsubmit = function (e) {
e.preventDefault();
var accountIsMobile = strategies.isMobile(account,'Mobile phone number format error');
var pwdMinLength = strategies.minLength(pwd,8.'Password must be no less than 8 characters');
var errorMsg = accountIsMobile||pwdMinLength;
if(errorMsg){
alert(errorMsg);
return false; }}Copy the code
Comparing the two implementations, we can see that code that has separated the validation logic can be used by adding new definitions to the policy group if it needs to extend the validation type. If you need to modify the verification implementation, directly modify the corresponding policy to take effect globally. There are significant efficiency improvements for development and maintenance.
Extension: Epic form validation
Async-validator is an epic form validator encapsulated by Element-UI and ANTD
Through the comparison of form verification, I believe that everyone has some understanding of the policy mode, so let’s take two examples to understand the application of the policy mode in JavaScript:
Use strategy mode to call Baidu AI image recognition
Because the interface type of Baidu AI image recognition is different, the required parameter format is not the same. However, image compression and upload, error handling and other parts are common. Therefore, policy mode encapsulation can be adopted:
Defining a Policy Group
Policy groups are defined to encapsulate different interfaces and their parameters: for example, side field of ID card identification interface, templateSign field of custom identification, and poparamstData as the receiving parameter of driving pass identification.
/** * Policy group * IDCARD: ID identification * CUSTOMIZED: VL: ID identification */
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 } }; }};Copy the code
Defines the Context
var ImageReader = function () {};/** * get the result **@param {*} Type Indicates the file type *@param {*} Base64 Indicates the base64 code * of the file to be identified@param {*} CallBack identifies the result 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 () {
/** ** picture compression processing and exception processing, code omitted */
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);
// The unified error code exposed to the UI layer
if(data.error_code ! = =undefined&& data.error_code ! = =0) {
var errorData = {
error: 1.title: 'wrong' + data.error_code,
content: 'error message'
};
callBack(errorData);
} else{ callBack(data); }}}); }; };Copy the code
Call way
var imageReader = new ImageReader();
imageReader.getOcrResult('IDCARD'.this.result.toString(), callback);
Copy the code
Encapsulate the Vue Select component with the policy pattern
The Element-UI select component is used in several places in a project, with similar internal logic in that it gets the data source for the drop-down list at initialization and dispatches different actions when an item is selected. Therefore, the use of strategic mode encapsulation is considered.
Context
In this case, the component exposes a prop externally, which the caller specifies to load a different policy. Define the Context as follows:
<template>
<el-select v-model="selectedValue" placeholder="Please select" @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>
Copy the code
data() {
return {
selectedValue: undefined.options: [].action: ""}; },props: {
// Expose to external select-type
selectType: {
type: String}},created() {
/ / get the options
this.valuation();
},
methods: {
optionChanged() {
this.$emit(this.action, this.selectedValue);
},
setOptions(option) {
this.$store.dispatch(this.action, option);
},
valuation() {
// Get options data}},Copy the code
The external calls the component as follows:
<MySelect selectType="product"/>
Copy the code
strategies
Then define the policy group:
let strategies = {
source: {
action: "sourceOption".getOptions: function() {
/ / pull options}},product: {
action: "productOption".getOptions: function() {
/ / pull options}},... }Copy the code
asynchronous
At this point, the basic structure of the component is clear, but there is a problem: when the component is loaded, the options are pulled asynchronously, and when the page is initialized, it is possible that the options are not returned, causing the options of the select to remain empty. So you should change the code here and get options synchronously:
// Modify the policy group
source: {
action: "sourceOption".getOptions: async function() {
// await pull options}},// Component modification
methods: {...async valuation(){... }}Copy the code
Continue to optimize
However, we do not need to pull options every time we load a component. If these options are used in other components or pages, we can consider storing them in VUex.
The original idea was to define a high-order component, that is, to define a wrapped SELECT template that extends its data source and action (the variable part) in a high-order component way. However, this idea was not so vUE (mainly slots is not easy to deal with), so we considered the policy pattern to rewrite this component
conclusion
From the above two examples, we can see:
- The strategic pattern conforms to the open-closed principle
- If you have to write a lot of code
if-else
Statement, then consider using the policy pattern - Consider the policy pattern if the only difference between multiple components (classes) is their behavior
Refer to JavaScript design patterns and development practices (explored) in Chapter 5