JavaScript策略模式

news2024/11/25 13:16:12

JavaScript策略模式

  • 1 什么是策略模式
  • 2 实现一个基础的策略模式
  • 3 Javascript中策略模式
  • 4 使用策略模式实现缓动动画
  • 5 使用策略模式实现表单校验

1 什么是策略模式

策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,将每个算法都封装起来,并且使它们可以相互替换。

策略模式让算法独立于使用它的客户而独立变化。

2 实现一个基础的策略模式

下面以计算奖金的例子来介绍策略模式。比如说,绩效为S的人年终奖有4倍工资,绩效为A的人年终奖有3倍工资,绩效为B的人年终奖为2倍工资。

根据上述条件,我们有如下的实现,编写一个名为calcBouns的函数来计算年终奖,很显然该函数需要接受两个参数:这个人的绩效等级以及他的工资数。

var calcBonus = function (level, salary) {
  if (level === "S") {
    return salary * 4;
  }
  if (level === "A") {
    return salary * 3;
  }
  if (level === "B") {
    return salary * 2;
  }
};

这段代码虽然简单,但是存在着一些缺点:
1、calcBonus函数包含了很多if-else语句,这些语句需要覆盖所有的逻辑分支
2、缺乏弹性,如果增加了一种新的绩效等级C,或者想把绩效S的奖金系数改为5,那我们必须深入函数的内部实现
3、算法的复用性差


了解了以上代码的缺点之后,我们需要对这些代码进行重构。首先使用组合函数重构代码,我们将各种条件封装到每个函数中,使其可以被很好的复用,示例如下:

var levelS = function (salary) {
  return salary * 4;
};

var levelA = function (salary) {
  return salary * 3;
};

var levelB = function (salary) {
  return salary * 2;
};

var calcBonus = function (level, salary) {
  if (level === "S") {
    return levelS(salary);
  }
  if (level === "A") {
    return levelA(salary);
  }
  if (level === "B") {
    return levelB(salary);
  }
};

上面的代码将复用性得到了改善,但是calcBonus函数有可能会变得越来越庞大的问题没有解决,这时有更好的办法,就是使用策略模式来重构代码。


一个基于策略模式的程序至少由两部分组成:

  1. 一组策略类,策略类封装了具体的算法,并负责具体的计算过程
  2. 环境类ContextContext接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明 Context中要维持对某个策略对象的引用

首先把每种绩效的计算规则都封装在对应的策略类里面:

var levelS = function () {};
levelS.prototype.calculate = function (salary) {
  return salary * 4;
};

var levelA = function () {};
levelA.prototype.calculate = function (salary) {
  return salary * 3;
};

var levelB = function () {};
levelB.prototype.calculate = function (salary) {
  return salary * 2;
};

然后定义一个奖金类Bouns

var Bonus = function () {
  this.salary = null; // 原始工资
  this.strategy = null; // 绩效等级对应的策略对象
};

Bonus.prototype.setSalary = function (salary) {
  this.salary = salary; // 设置员工的原始工资
};

Bonus.prototype.setStrategy = function (strategy) {
  this.strategy = strategy; // 设置员工绩效等级对应的策略对象
};

Bonus.prototype.getBonus = function () {
  // 取得奖金数额
  return this.strategy.calculate(this.salary); // 把计算奖金的操作委托给对应的策略对象
};

现在我们来完成这个例子中剩下的代码。先创建一个bonus对象,并且给bonus对象设置员工的原始工资数额,接下来把某个计算奖金的策略对象也传入bonus对象内部保存起来。当调bonus.getBonus()来计算奖金的时候,bonus对象本身并没有能力进行计算,而是把请求委托给了之前保存好的策略对象:

var bonus = new Bonus(); // 创建Bonus实例对象
bonus.setSalary(1000); // 设置员工的初始工资

bonus.setStrategy(new levelS()); // 设置策略对象
console.log(bonus.getBonus()); // 输出:4000

bonus.setStrategy(new levelA()); // 设置策略对象
console.log(bonus.getBonus()); // 输出:3000

可以看到通过策略模式重构之后,代码变得更加清晰,各个类的职责更加鲜明。

3 Javascript中策略模式

JavaScript语言中,函数也是对象,所以更简单和直接的做法是把计算规则直接定义为对象:

var level= {
  S: function (salary) {
    return salary * 4;
  },
  A: function (salary) {
    return salary * 3;
  },
  B: function (salary) {
    return salary * 2;
  },
};

依然用calcBonus函数充当环境类Context来接受用户的请求,代码结构更加简洁:

var calceBonus = function (level, salary) {
  return level[level](salary);
};

4 使用策略模式实现缓动动画

JavaScript中实现动画效果,可以通过连续改变元素的某个CSS属性,比如lefttopbackground-position来实现动画效果。

例如,编写一个动画类和缓动算法,让正方形在页面中运动起来。下面是一些常见的缓动算法,这些算法都接受4个参数,这4个参数的含义分别是:动画已消耗的时间、原始位置、目标位置、动画持续的总时间,返回的值是动画元素应该处在的当前位置。

var tween = {
  linear: function (t, b, c, d) {
    return (c * t) / d + b;
  },
  easeIn: function (t, b, c, d) {
    return c * (t /= d) * t + b;
  },
  strongEaseIn: function (t, b, c, d) {
    return c * (t /= d) * t * t * t * t + b;
  },
  strongEaseOut: function (t, b, c, d) {
    return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
  },
  sineaseIn: function (t, b, c, d) {
    return c * (t /= d) * t * t + b;
  },
  sineaseOut: function (t, b, c, d) {
    return c * ((t = t / d - 1) * t * t + 1) + b;
  },
};

现在来分析实现这个程序的思路。在运动开始之前,我们了解以下信息:

  • 动画开始时,正方形所在的原始位置
  • 正方形移动的目标位置
  • 动画开始时的准确时间点
  • 正方形运动持续的时间

随后,我们会用setInterval创建一个定时器,定时器每隔19ms循环一次。在定时器的每一帧里,我们会把动画已消耗的时间、正方形原始位置、正方形目标位置和动画持续的总时间等信息传入缓动算法。该算法会通过这几个参数,计算出正方形当前应该所在的位置。最后再更新该div对应的CSS属性,正方形就能够顺利地运动起来了。

现在我们开始编写完整的代码,首先在页面中放置一个div

<div class="div1">橘猫吃不胖</div>
.div1 {
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: orange;
}

接下来定义Animate类,Animate的构造函数接受一个参数:即将运动起来的dom节点。Animate类的代码如下:

var Animate = function (dom) {
  this.dom = dom; // 进行运动的dom节点
  this.startTime = 0; // 动画开始时间
  this.startPos = 0; // 动画开始时,dom的初始位置
  this.endPos = 0; // 动画结束时,dom的目标位置
  this.propertyName = null; // dom节点需要被改变的css属性名
  this.easing = null; // 缓动算法
  this.duration = null; // 动画持续时间
};

接下来定义Animate.prototype.start方法,它负责启动这个动画,在动画被启动的瞬间,要记录一些信息,供缓动算法在以后计算小球当前位置的时候使用。在记录完这些信息之后,此方法还要负责启动定时器。代码如下:

// propertyName:要改变的CSS属性名,比如'left'、'top',分别表示左右移动和上下移动
// endPos:正方形运动的目标位置
// duration:动画持续时间。
// easing:缓动算法。
Animate.prototype.start = function (propertyName, endPos, duration, easing) {
  this.startTime = +new Date(); // 动画启动时间
  this.startPos = this.dom.getBoundingClientRect()[propertyName]; // dom节点初始位置
  this.propertyName = propertyName; // dom节点需要被改变的CSS属性名
  this.endPos = endPos; // dom节点目标位置
  this.duration = duration; // 动画持续事件
  this.easing = tween[easing]; // 缓动算法

  var self = this;
  var timeId = setInterval(function () {
    // 启动定时器,开始执行动画
    if (self.step() === false) {
      // 如果动画已结束,则清除定时器
      clearInterval(timeId);
    }
  }, 19);
};

然后是定义Animate.prototype.step方法,该方法代表正方形运动的每一帧要做的事情。在此处,这个方法负责计算小球的当前位置,和调用更新CSS属性值的方法Animate.prototype.update

// 定义正方形每一帧要做的事情
Animate.prototype.step = function () {
  var time = +new Date(); // 取得当前时间
  // 如果当前时间>动画开始时间+动画持续时间,说明动画已经结束了
  // 此时修正正方形的位置就可以了
  if (time >= this.startTime + this.duration) {
    this.update(this.endPos); // 更新正方形的CSS属性值
    return false; // 清除定时器标识
  }
  // 如果动画没有结束,根据缓动动画计算正方形的位置
  var pos = this.easing(
    time - this.startTime,
    this.startPos,
    this.endPos - this.startPos,
    this.duration
  );
  // pos为正方形当前位置
  this.update(pos); // 更新正方形的CSS属性值
};

更新CSS属性的方法:

// 更新正方形CSS属性
Animate.prototype.update = function (pos) {
  this.dom.style[this.propertyName] = pos + "px";
};

到这里缓动动画的实现就结束了,我们在页面中实验一下:

var div = document.querySelector(".div1");
var animate = new Animate(div);
// 2s内向右移动500px
animate.start("left", 500, 2000, "strongEaseOut");

在这里插入图片描述

5 使用策略模式实现表单校验

假设我们正在编写一个注册的页面,在点击注册按钮之前,有如下几条校验逻辑:

  • 用户名不能为空
  • 密码长度不能少于6位
  • 手机号码必须符合格式

首先是一版没有引入策略模式的代码:

<form id="registerForm">
  请输入用户名:<input type="text" name="userName"/ > 请输入密码:<input
  type="text" name="password"/ > 请输入手机号码:<input type="text"
  name="phoneNumber"/ >
  <button>提交</button>
</form>
var registerForm = document.getElementById("registerForm");

registerForm.onsubmit = function () {
  if (registerForm.userName.value === "") {
    alert("用户名不能为空");
    return false;
  }
  if (registerForm.password.value.length < 6) {
    alert("密码长度不能少于 6 位");
    return false;
  }
  if (!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) {
    alert("手机号码格式不正确");
    return false;
  }
};

这段代码的缺点跟计算奖金的最初版本一模一样,接下来我们使用策略模式来重构代码。首先我们要把这些校验逻辑都封装成策略对象:

var strategies = {
  isNonEmpty: function (value, errorMsg) {
    // 字段不为空
    if (value === "") {
      return errorMsg;
    }
  },
  minLength: function (value, length, errorMsg) {
    // 输入内容限制最小长度
    if (value.length < length) {
      return errorMsg;
    }
  },
  isMobile: function (value, errorMsg) {
    // 手机号码格式是否正确
    if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
      return errorMsg;
    }
  },
};

接下来我们实现环境类Context,也就是Validator类,它负责接收用户的请求并委托给strategy对象。

var Validator = function () {
  this.cache = []; // 保存校验规则
};

// 通过add添加校验方法
// dom;参与校验的表单dom,即表单字段
// rule:当前表单字段的校验规则
// errorMsg:校验未通过时返回的错误信息
Validator.prototype.add = function (dom, rule, errorMsg) {
  // rule接收xxxx:xx的形式的字符串
  var ary = rule.split(":"); // 把strategy和参数分开
  this.cache.push(function () {
    // 把校验的步骤用空函数包装起来,并且放入cache
    var strategy = ary.shift(); // 用户挑选的strategy
    ary.unshift(dom.value); // 把表单项的value添加进参数列表
    ary.push(errorMsg); // 把errorMsg添加进参数列表
    return strategies[strategy].apply(dom, ary);
  });
};

// 启动校验规则
Validator.prototype.start = function () {
  // 根据校验规则的数量,挨个检验
  for (var i = 0, validatorFunc; (validatorFunc = this.cache[i++]); ) {
    var msg = validatorFunc(); // 开始校验,并取得校验后的返回信息
    if (msg) {
      // 如果有确切的返回值,说明校验没有通过
      return msg;
    }
  }
};

到这里就使用策略模式改写完成了,接下来使用一下,给提交按钮绑定submit事件,其中包含校验表单的逻辑:

// 提交表单时校验每个字段
var validataFunc = function () {
  var validator = new Validator(); // 创建一个validator对象

  // 添加一些校验规则
  validator.add(registerForm.userName, "isNonEmpty", "用户名不能为空");
  validator.add(
    registerForm.password,
    "minLength:6",
    "密码长度不能少于 6 位"
  );
  validator.add(
    registerForm.phoneNumber,
    "isMobile",
    "手机号码格式不正确"
  );

  var errorMsg = validator.start(); // 获得校验结果
  return errorMsg; // 返回校验结果
};

var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function () {
  var errorMsg = validataFunc(); // 如果errorMsg有确切的返回值,说明未通过校验
  if (errorMsg) {
    alert(errorMsg);
    return false; // 阻止表单提交
  }
};

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1020400.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

webpack配置alias后eslint和ts无法识别

背景 我们在 webpack 配置 alias 后&#xff0c;发现项目中引入的时候&#xff0c;还是会报错&#xff0c;如下&#xff1a; 可以看到&#xff0c;有一个是 ts报错&#xff0c;还有一个是 eslint 报错。 解决 ts 报错 tsconfig.json {"compilerOptions": {...&q…

2023 Google 开发者大会,共创、赋能开发者

前言&#xff1a; 9月6日&#xff0c;2023 Google 开发者大会在上海拉开帷幕。在本次大会&#xff0c;Google 将技术灵感带到了中国。在为期两天的大会中&#xff0c;让我印象最为深刻的是&#xff0c;谷歌帮助中国开发者释放潜能&#xff0c;持续创新&#xff0c;落地创意灵感…

【C语言】每日一题(半月斩)——day3

目录 一&#xff0c;选择题 1.已知函数的原型是&#xff1a; int fun(char b[10], int *a); 2、请问下列表达式哪些会被编译器禁止【多选】&#xff08; &#xff09; 3、以下程序的输出结果为&#xff08; &#xff09; 4、下面代码段的输出是&#xff08; &#xff09;…

Chatbot到底提供了哪些便利?来看看“中文版Chatbase”

Chatbot的出现可以说是在极大的程度上改变了企业与客户互动的方式。Chatbot凭借其先进的功能和全天候可用性提供了一系列便捷的功能&#xff0c;为企业和客户提供便利和高效。随着自然语言处理和机器学习算法的进步&#xff0c;Chatbot已经发展到可以提供准确和个性化的响应&am…

【Seata】分布式事务问题和理论基础

目录 1.分布式事务问题 1.1本地事务 1.2分布式事务 2.理论基础 2.1CAP定理 2.1.1一致性 2.1.2可用性 2.1.3分区容错 2.1.4矛盾 2.2BASE理论 2.3解决分布式事务的思路 1.分布式事务问题 1.1本地事务 本地事务&#xff0c;也就是传统的单机事务。在传统数据库事务中…

软件包的管理

概念 在早期Linux系统中&#xff0c;要想在Linux系统中安装软件只能采取编译源码包的方式进行安装&#xff0c;所以早期安装软件是一件非常困难、耗费耐心的事情&#xff0c;而且大多数服务程序仅提供源代码&#xff0c;还需要运维人员编译后自行解决软件之间的依赖关系。所以…

【力扣每日一题】2023.9.18 打家劫舍Ⅲ

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 今天是打家劫舍3&#xff0c;明天估计就是打家劫舍4了。 今天的打家劫舍不太一样&#xff0c;改成二叉树了&#xff0c;不过规则没有变&…

苹果手机无法正常使用小程序和APP

小程序、APP 已使用了几年&#xff0c;突然大量反馈&#xff1a;苹果手机无法正常使用。但不是全部&#xff0c;只是部分手机。 因为同事苹果手机都能用&#xff0c;所以无法准确判断具体原因。 后来同事苹果手机也无法使用了&#xff0c;显示&#xff1a; 网上搜索结果&…

力扣刷题19-删除链表的倒数第N个节点

题目来源 题目描述&#xff1a; class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {//为了删除的格式一样&#xff0c;引入虚拟头节点ListNode dummyNodenew ListNode(1);dummyNode.nexthead;ListNode slowdummyNode;ListNode fastdummyNode;for(int…

java项目之交通事故档案管理系统(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的交通事故档案管理系统。技术交流和部署相关看文章末尾&#xff01; 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java 框…

github desktop上传代码

这个很难受&#xff0c;因为遇到了很多问题。最终选择的方式如下&#xff1a; 1&#xff1a;在GitHub上搞一个新建的“repositories”. 这个repositories可以是已经存在的或随便在什么地方新建的。不要紧 2&#xff1a;在“github desktop”上把上面的“repositories”项目cl…

玩转安卓运行速度优化

原文链接 玩转安卓运行速度优化 早在许多年以前写过一篇安卓性能优化文章&#xff0c;时过境迁&#xff0c;很多事情都有了变化&#xff0c;所以再专门针对程序运行速度和渲染的优化&#xff0c;这两方面非常直接的影响应用程序的操作流畅度&#xff0c;也可以称作流畅度优化方…

vue Router路由

编程式导航 | Vue Router 看官方文档 vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成&#xff0c;让用 Vue.js 构建单页应用变得轻而易举。功能包括&#xff1a; 嵌套路由映射动态路由选择模块化、基于组件的路由配置路由参数、查询、通配符展示由 Vue.js 的过…

Golang Linux 安装与环境变量配置

下载 Go 二进制包 wget https://dl.google.com/go/go1.21.1.linux-amd64.tar.gz 解压文件并将其移至 /usr/local 目录 sudo tar -C /usr/local -xzf go1.20.1.linux-amd64.tar.gz -C 选项解压文件到 /usr/local 目录&#xff0c;查看 /usr/local/go 目录的内容 将 Go 二进…

【openwrt学习笔记】miniupnpd学习笔记

目录 一、参考资料二、upnp基本概念三、miniupnpd3.1参数说明3.1.1 config upnp 的配置参数表3.1.2 config perm_rule 许可设置配置参数表 3.2 windows上使用miniupnpc工具3.3 关于开启upnp功能后无法添加端口 四、其它知识补充4.1 NAT4.1.1 NAT概念4.1.2 NAT的分类 4.2 NAT穿透…

华为云云耀云服务器L实例评测|伪分布式环境下部署hadoop2.10.1

文章目录 前言云耀云服务器L实例简介Hadoop简介 一、配置环境购买云耀云服务器L实例查看云耀云服务器L实例状态重置密码查看弹性公网IP地址 FinalShell连接服务器二、搭建Hadoop单机版本详细安装步骤如下&#xff1a;我们先开始配置java环境hadoop2.x接下来需要利用vim来操作co…

Java常用类之 Java比较器、System类、Math类、BigInteger与BigDecimal

Java常用类 文章目录 五、Java比较器5.1、Comparable 接口的使用5.1.1、自然排序&#xff1a;Comparable接口5.1.2、定制排序&#xff1a;Comparator接口 六、System类七、Math类八、BigInteger与BigDecimal 五、Java比较器 提出背景&#xff1a; Java中的对象&#xff0c;正常…

泰尔指数案例分析

泰尔指数是一种衡量‘不平均’的指数&#xff0c;比如用于衡量‘贫富差异’&#xff0c;也或者衡量大气污染的水平是否一致&#xff0c;二氧化碳排放水平差异情况等。泰尔指数的数学原理是‘熵’&#xff0c;‘熵’是一种衡量数据‘有序性’的指标&#xff0c;当‘熵’值越大时…

GEE:快速实现NDVI时间序列NDVI线性趋势和变化敏感性计算(斜率、截距)

作者:CSDN @ _养乐多_ 本博客将向您介绍如何使用Google Earth Engine(GEE)平台来处理Landsat 5、7和8的卫星图像数据,计算NDVI的斜率和截距,以及如何导出这些结果供进一步分析使用。 文章目录 一、代码详解1.1 核心代码详解1.2 核心代码详解1.3 代码框架介绍二、完整代码…

buuctf web [极客大挑战 2019]Http

进入题目上下翻找了一下&#xff0c;没有什么突破口 检查了一下源码&#xff0c;有一个跳转页面 点击页面&#xff0c;跳转到了新的地方 新页面里没有别的跳转接口 但是页面中有提示&#xff1a;It doesnt come from https://Sycsecret.buuoj.cn 打开burp 页面提示要求来自h…