Шаблон стратегии "Hand Touch Design Pattern Series" и динамическая проверка формы

Шаблоны проектирования JavaScript
Шаблон стратегии "Hand Touch Design Pattern Series" и динамическая проверка формы

режим стратегии(Шаблон стратегии), также известный как шаблон политики, определяет ряд алгоритмов, инкапсулирует их один за другим и делает их взаимозаменяемыми. Алгоритм инкапсулированной стратегии, как правило, независим, и режим стратегии регулирует используемый алгоритм в зависимости от входных данных. Ключ стратегическийРазделение реализации и использования.

Уведомление:В этой статье могут использоваться некоторые методы кодирования, такие какIIFE(Выражение немедленно вызываемой функции, Выражение немедленно вызываемой функции), синтаксис ES6let/const,стрелочная функция,остаточный параметр,оператор короткого замыканияПодождите, если вы не трогали его, вы можете нажать на ссылку, чтобы немного узнать~

1. Паттерны стратегии, которые вы видели

В настоящее время есть много видов электронных продуктов и различных размеров, иногда вы не можете не хотеть разобрать их, чтобы увидеть, что внутри (вспомните игрушечную машину и пульт дистанционного управления, которые вы разобрали, когда были ребенком), но есть Есть много спецификаций винтов, и размер отвертки не одинаков Нет, если вы будете покупать отвертку каждый раз, когда сталкиваетесь со спецификацией, ваш дом будет полон отверток. Поэтому люди сейчас используют многофункциональные наборы отверток.Нужна только одна ручка отвертки.При встрече с винтами разных спецификаций просто меняйте головку отвертки.Это очень удобно и объем намного меньше.

В качестве другого примера, у автомобиля есть много спецификаций шин: вы можете использовать грязевые шины, когда много ездите по грязным дорогам, использовать зимние шины, когда много ездите по снегу, и использовать высокоскоростные шины, когда много ездите по грязным дорогам. Высокопроизводительные шины можно заменять другими шинами для различных сценариев использования, не заменяя весь автомобиль.

Все это примеры шаблонов стратегий. Отвертки/автомобили относятся к контексту упаковки, а разные головки/шины отверток упаковываются и используются. Головки/шины отверток здесь эквивалентны стратегиям, и разные стратегии использования могут быть заменены в соответствии с разными потребности.

В этих сценариях существуют следующие характеристики:

  1. Головки/шины отверток (стратегии) ​​независимы друг от друга, но могут быть заменены друг другом;
  2. Отвертка/автомобиль (контекст пакета) может использовать разные стратегии в соответствии с разными потребностями;

2. Кодовая реализация примера

Мы используем примеры программирования для демонстрации конкретных примеров, которые легче поддаются количественной оценке.

Сцена такова: веб-сайт электронной коммерции хочет провести мероприятие по продаже товаров со скидками, некоторые товары стоят более 100 минус 30, некоторые товары стоят более 200 минус 80, а некоторые товары напрямую продаются со скидкой 20% ( вспоминается по двойному страху доминирования одиннадцати), эта логика передается нам, как мы ее реализуем.

function priceCalculate(discountType, price) {
    if (discountType === 'minus100_30') {   		// 满100减30
        return price - Math.floor(price / 100) * 30
    }
    else if (discountType === 'minus200_80') {  // 满200减80
        return price - Math.floor(price / 200) * 80
    }
    else if (discountType === 'percent80') {    // 8折
        return price * 0.8
    }
}

priceCalculate('minus100_30', 270)    // 输出: 210
priceCalculate('percent80', 250)      // 输出: 200

Судя по типу скидки, введенной для расчета способа расчета общей стоимости продукта, несколькоif-elseОн удовлетворяет потребности, но очевидны и недостатки такого подхода:

  1. priceCalculateФункция увеличивается с типом скидки,if-elseСуждение будет становиться все более и более раздутым;
  2. Изменения требуются, если добавляется новый тип скидки или изменяется алгоритм типа скидкиpriceCalculateРеализация функции, нарушающей принцип открыто-закрыто;
  3. Плохая возможность повторного использования: если в других местах есть похожие алгоритмы, но правила другие, приведенный выше код нельзя использовать повторно;

Мы можем изменить его, чтобы рассчитать скидкуЧасть алгоритма извлеченасохранить как объект, со скидкойвведите как ключ, при такой индексацииВызов конкретного алгоритма через индекс ключ-значение объекта:

const DiscountMap = {
    minus100_30: function(price) {
        return price - Math.floor(price / 100) * 30
    },
    minus200_80: function(price) {
        return price - Math.floor(price / 200) * 80
    },
    percent80: function(price) {
        return price * 0.8
    }
}

/* 计算总售价*/
function priceCalculate(discountType, price) {
    return DiscountMap[discountType] && DiscountMap[discountType](price)
}

priceCalculate('minus100_30', 270)
priceCalculate('percent80', 250)

// 输出: 210
// 输出: 200

такРеализация алгоритма и использование алгоритма разделены, добавлять новые алгоритмы тоже очень просто:

DiscountMap.minus150_40 = function(price) {
    return price - Math.floor(price / 150) * 40
}

Если вы хотите, чтобы алгоритм расчета был скрыт, вы можете использовать метод закрытия с IIFE, В это время вам нужно добавить запись для увеличения стратегии, чтобы облегчить расширение:

const PriceCalculate = (function() {
    /* 售价计算方式 */
    const DiscountMap = {
        minus100_30: function(price) {      // 满100减30
            return price - Math.floor(price / 100) * 30
        },
        minus200_80: function(price) {      // 满200减80
            return price - Math.floor(price / 200) * 80
        },
        percent80: function(price) {        // 8折
            return price * 0.8
        }
    }
    
    return {
        priceClac: function(discountType, price) {
            return DiscountMap[discountType] && DiscountMap[discountType](price)
        },
        addStrategy: function(discountType, fn) {		// 注册新计算方式
            if (DiscountMap[discountType]) return
            DiscountMap[discountType] = fn
        }
    }
})()

PriceCalculate.priceClac('minus100_30', 270)	// 输出: 210

PriceCalculate.addStrategy('minus150_40', function(price) {
    return price - Math.floor(price / 150) * 40
})
PriceCalculate.priceClac('minus150_40', 270)	// 输出: 230

Таким образом, алгоритм скрыт, а запись для добавления стратегий зарезервирована для легкого расширения.

3. Общая реализация схемы стратегии

В соответствии с приведенным выше примером для уточнения режима стратегии метод расчета скидки можно рассматривать как стратегию (Стратегия), и эти стратегии можно заменять друг другом, а конкретный процесс расчета скидки можно рассматривать как контекст инкапсуляции (Контекст ), и при необходимости можно использовать контекст инкапсуляции. Выберите другую стратегию.

В основном это следующие понятия:

  1. Context: инкапсулировать контекст, вызывать требуемую стратегию по мере необходимости, защищать прямой вызов стратегии от внешнего мира, предоставлять только интерфейс для внешнего мира и вызывать соответствующую стратегию по мере необходимости;
  2. Strategy: стратегия, которая содержит определенные алгоритмы, методы которых имеют одинаковый внешний вид, поэтому их можно заменять друг другом;
  3. StrategyMap: набор всех стратегий для вызова контекста инкапсуляции;

Структурная схема выглядит следующим образом:

策略模式

Ниже используется обобщенный метод для ее достижения.

const StrategyMap = {}

function context(type, ...rest) {
    return StrategyMap[type] && StrategyMap[type](...rest)
}

StrategyMap.minus100_30 = function(price) { 
  	return price - Math.floor(price / 100) * 30
}

context('minus100_30', 270)			// 输出: 210

Общая реализация кажется относительно простой, вот реальный проект, которым можно поделиться.

4. Стратегический режим в реальном бою

4.1 Форматировщик форм

Вот пример, используемый в проекте Vue + ElementUI Принципы проекта других фреймворков аналогичны, и я поделюсь с вами.

Элементконтроль формыКолонка принимаетformatterПараметр, используемый для форматирования содержимого, относится к типу function и также может принимать несколько конкретных параметров, например:Function(row, column, cellValue, index).

Взяв в качестве примера преобразование размера файла, серверная часть часто напрямую передает размер файла в битовых единицах, поэтому клиентской части необходимо преобразовать данные в серверной части в размер файла в необходимых ему единицах, таких как КБ. /MB, в соответствии с его потребностями.

Сначала реализуем алгоритм расчета файла:

export const StrategyMap = {
    /* Strategy 1: 将文件大小(bit)转化为 KB */
    bitToKB: val => {
        const num = Number(val)
        return isNaN(num) ? val : (num / 1024).toFixed(0) + 'KB'
    },
    /* Strategy 2: 将文件大小(bit)转化为 MB */
    bitToMB: val => {
        const num = Number(val)
        return isNaN(num) ? val : (num / 1024 / 1024).toFixed(1) + 'MB'
    }
}

/* Context: 生成el表单 formatter */
const strategyContext = function(type, rowKey){ 
  return function(row, column, cellValue, index){
  	return StrategyMap[type](row[rowKey])
  }
}

export default strategyContext

Затем в компоненте мы можем напрямую:

<template>
    <el-table :data="tableData">
        <el-table-column prop="date" label="日期"></el-table-column>
        <el-table-column prop="name" label="文件名"></el-table-column>
        <!-- 直接调用 strategyContext -->
        <el-table-column prop="sizeKb" label="文件大小(KB)"
                         :formatter='strategyContext("bitToKB", "sizeKb")'>
        </el-table-column>
        <el-table-column prop="sizeMb" label="附件大小(MB)"
                         :formatter='strategyContext("bitToMB", "sizeMb")'>
        </el-table-column>
    </el-table>
</template>

<script type='text/javascript'>
    import strategyContext from './strategyContext.js'
    
    export default {
        name: 'ElTableDemo',
        data() {
            return {
                strategyContext,
                tableData: [
                    { date: '2019-05-02', name: '文件1', sizeKb: 1234, sizeMb: 1234426 },
                    { date: '2019-05-04', name: '文件2', sizeKb: 4213, sizeMb: 8636152 }]
            }
        }
    }
</script>

<style scoped></style>

Примеры кода см.codepen - режим стратегии в действии

Результат работы следующий:

4.2 Проверка формы

В дополнение к форматтеру в таблице шаблон стратегии часто используется в сценариях проверки формы.Вот пример проекта Vue + ElementUI.То же самое верно и для других фреймворков.

ElementUIФорма формыОн имеет функцию проверки формы, которая используется для проверки содержимого формы, введенного пользователем. В реальных требованиях элементы проверки формы, как правило, более сложны, поэтому необходимо добавить настраиваемый метод проверки валидатора к каждому элементу формы.

нам может понравитьсяПример официального сайтаНапишите проверку формы в состоянии компонента, а такжеdataВ функции, но это затрудняет повторное использование метода проверки формы, который используется чаще.В настоящее время мы можем реорганизовать его, объединив знания режима стратегии и каррирования функций. Прежде всего, мы находимся в инструментальном модуле проекта (обычноutilsпапка) для реализации универсального метода проверки формы:

// src/utils/validates.js

/* 姓名校验 由2-10位汉字组成 */
export function validateUsername(str) {
    const reg = /^[\u4e00-\u9fa5]{2,10}$/
    return reg.test(str)
}

/* 手机号校验 由以1开头的11位数字组成  */
export function validateMobile(str) {
    const reg = /^1\d{10}$/
    return reg.test(str)
}

/* 邮箱校验 */
export function validateEmail(str) {
    const reg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
    return reg.test(str)
}

затем вutils/index.jsДобавьте метод каррирования для создания функции проверки формы:

// src/utils/index.js

import * as Validates from './validates.js'

/* 生成表格自定义校验函数 */
export const formValidateGene = (key, msg) => (rule, value, cb) => {
    if (Validates[key](value)) {
        cb()
    } else {
        cb(new Error(msg))
    }
}

надformValidateGeneФункция принимает два параметра, первый — правило проверки, котороеsrc/utils/validates.jsИмя метода общего правила проверки, извлеченное из файла, а второй параметр — это подсказка для проверки формы, если сообщается об ошибке.

<template>
    <el-form ref="ruleForm"
             label-width="100px"
             class="demo-ruleForm"
             :rules="rules"
             :model="ruleForm">
        
        <el-form-item label="用户名" prop="username">
            <el-input v-model="ruleForm.username"></el-input>
        </el-form-item>
        
        <el-form-item label="手机号" prop="mobile">
            <el-input v-model="ruleForm.mobile"></el-input>
        </el-form-item>
        
        <el-form-item label="邮箱" prop="email">
            <el-input v-model="ruleForm.email"></el-input>
        </el-form-item>
    </el-form>
</template>

<script type='text/javascript'>
    import * as Utils from '../utils'
    
    export default {
        name: 'ElTableDemo',
        data() {
            return {
                ruleForm: { pass: '', checkPass: '', age: '' },
                rules: {
                    username: [{
                        validator: Utils.formValidateGene('validateUsername', '姓名由2-10位汉字组成'),
                        trigger: 'blur'
                    }],
                    mobile: [{
                        validator: Utils.formValidateGene('validateMobile', '手机号由以1开头的11位数字组成'),
                        trigger: 'blur'
                    }],
                    email: [{
                        validator: Utils.formValidateGene('validateEmail', '不是正确的邮箱格式'),
                        trigger: 'blur'
                    }]
                }
            }
        }
    }
</script>

Видно, что его очень удобно использовать.Метод проверки формы извлекается как стратегия, а метод каррирования используется для динамического выбора метода проверки формы, чтобы гибко использовать стратегию и значительно ускорить эффективность разработки. .

Примеры кода см.codeandbox — проверка формы в режиме стратегии в действии

результат операции:

5. Преимущества и недостатки паттерна стратегии

Паттерн стратегии сочетает в себе алгоритмВнедряйте и используйте сплиты, эта функция дает много преимуществ:

  1. стратегии не зависят друг от друга, ноСтратегии можно свободно переключать, характеристики этого режима стратегии делают режим стратегии более гибким, а также повышают скорость повторного использования стратегии;
  2. Если режим стратегии не используется, при выборе стратегии обычно используются множественные условные суждения.Избегайте множественных условных суждений, повысить ремонтопригодность;
  3. Хорошая масштабируемость, стратегию можно легко расширить;

Недостатки шаблона стратегии:

  1. Стратегии независимы друг от друга, поэтому некоторая сложная логика алгоритмаНе могу поделиться, что приводит к некоторой трате ресурсов;
  2. Если пользователь хочет принять какую стратегию, он должен понять реализацию стратегии, поэтомуВсе политики должны быть раскрыты, что нарушает Закон Деметры/принцип наименьшего знания, а также увеличивает стоимость использования объектов политики для пользователей.

6. Применимые сценарии режима стратегии

Итак, в каких сценариях следует использовать шаблон стратегии:

  1. несколько алгоритмовтолько немного отличается в поведенииВ этом случае можно использовать режим стратегии для динамического выбора алгоритма;
  2. алгоритмнужно свободно переключатьсяместо действия;
  3. иногдаТребуется несколько условных суждений, то вы можете использовать режим стратегии, чтобы избежать ситуации множественных суждений условия;

7. Другие связанные режимы

7.1 Шаблон стратегии и шаблон метода шаблона

Шаблон стратегии похож на шаблон метода шаблона, но структура и реализация немного отличаются.

  1. режим стратегииДавайте динамически укажем алгоритм, который будет использоваться во время работы программы;
  2. Шаблон метода шаблонаИспользуемый алгоритм был определен при определении подкласса;

7.2 Модель стратегии и модель наилегчайшего веса

См. введение в Шаблоны наилегчайшего веса.


Большинство сообщений в Интернете имеют разную глубину и даже некоторые несоответствия. Следующие статьи являются кратким изложением процесса обучения. Если вы найдете какие-либо ошибки, пожалуйста, оставьте сообщение, чтобы указать ~

Эта статья из моей рубрикиОдна из статей, заинтересованные студенты могут щелкнуть ссылку, чтобы увидеть больше статей ~

PS: Всех приглашаю обратить внимание на мой публичный аккаунт [Front End Afternoon Tea], давайте работать вместе~

Кроме того, вы можете присоединиться к группе WeChat «Front-end Afternoon Tea Exchange Group», нажмите и удерживайте, чтобы определить QR-код ниже, чтобы добавить меня в друзья, обратите вниманиеДобавить группу, я заберу тебя в группу~