js设计模式-策略模式

117 阅读3分钟

定义

当我们要实现某个功能有多种方案可以选择时,我们可以定义一系列的算法,把他们一个个封装起来,并且使它们可以相互替换,这种解决方案就是策略模式。

例子

以策略模式计算奖金

例如,绩效为S的人年终奖有4倍工资,绩效为A的人年终奖有3倍工资,而绩效为B的人年终奖是2倍工资。假设财务部要求我们提供一段代码,来方便他们计算员工的年终奖。

  • 利用一等函数对象实现策略模式
  const S = (salary) => {
    return salary * 4
  }
  const A = (salary) => {
    return salary * 4
  }
  const B = (salary) => {
    return salary * 4
  }
  const calculateBonus = (level, salary) => {
    return level(salary)
  }
  console.log(calculateBonus(S, 20000))
  console.log(calculateBonus(A, 10000))
  • 封装策略对象
  // 策略对象
  const strategies = {
    S(salary){
      return salary * 4
    },
    A(salary){
      return salary * 4
    },
    B(salary){
      return salary * 4
    },
  }
  // Context方法
  const calculateBonus = (level, salary) => {
    return strategies[level](salary)
  }
  console.log(calculateBonus('S', 20000))
  console.log(calculateBonus('A', 10000))
实现缓动动画

编写一个动画类和一些缓动算法,让小球以各种各样的缓动效果在页面中运动

// index.vue
<template>
    <button @click="animate('linear')">
    linear
    </button>
    <button @click="animate('easeIn')">
    easeIn
    </button>
    <button @click="animate('strongEaseIn')">
    strongEaseIn
    </button>
    <button @click="animate('strongEaseOut')">
    strongEaseOut
    </button>
    <button @click="animate('sineaseIn')">
    sineaseIn
    </button>
    <button @click="animate('sineaseOut')">
    sineaseOut
    </button>
    <div style="position:relative">
    <div style="position:absolute;background:blue;" id="div">我是div</div>
    </div>
</template>

<script lang="ts">
import { Animate } from '@/utils/animate'
import { defineComponent } from 'vue'
export default defineComponent({
  methods: {
    animate(key) {
      const div = document.querySelector('#div')
      div.style.left = 0;
      const animate = new Animate(div)
      animate.start('left', 200, 1000, key)
    }
  }
})
</script>
// animate.js
/**
 * 缓动算法策略对象
 * t: 动画已消耗的时间
 * b: 小球原始位置
 * c: 小球目标位置
 * d: 动画持续的总时间
 */
const tween = {
  linear(t,b,c,d) {
    return c*t/d+b;
  },
  easeIn(t,b,c,d) {
    return c*(t/=d)*t+b;
  },
  strongEaseIn(t,b,c,d) {
    return c*(t/=d)*t*t*t*t+b;
  },
  strongEaseOut(t,b,c,d) {
    return c*((t = t/d-1)*t*t*t*t+1)+b;
  },
  sineaseIn(t,b,c,d) {
    return c*(t/=d)*t*t+b;
  },
  sineaseOut(t,b,c,d) {
    return c*((t = t/d-1)*t*t+1)+b;
  },
}
const getDate = () => {
  return new Date().getTime()
}
// Context类
export class Animate{
  constructor(dom) {
    this.dom = dom;
    this.startTime = 0;
    this.startPos = 0;
    this.endPos = 0;
    this.propertyName = null;
    this.easing = null;
    this.duration = null;
  }
  // 启动定时器
  start(propertyName, endPos, duration, easing) {
    this.startTime = getDate(); // 动画启动时间
    this.startPos = this.dom.getBoundingClientRect()[propertyName]; // dom节点初始位置
    this.propertyName = propertyName; // dom节点需要被改变的css属性名
    this.endPos = endPos; // dom节点目标位置
    this.duration = duration; // 动画持续时间
    this.easing = tween[easing]; // 缓动算法
    const timeId = setInterval(() => { // 启动定时器,开始执行动画
      if (this.step() === false) { // 如果动画已结束,清除定时器
        clearInterval(timeId)
      }
    }, 19)
  }
  step() {
    const t = getDate(); // 获取当前时间
    if (t >= this.startTime + this.duration) { // 动画结束,将小球放置最终位置
      this.update(this.endPos);
      return false
    }
    // 动画运行期间,通过缓动函数计算小球当前位置
    const pos = this.easing(t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration);
    this.update(pos)
  }
  update(pos) {
    this.dom.style[this.propertyName] = pos + 'px'
  }
}
表单校验

注册页面,符合下述要求:
1、用户名不能为空。2、密码长度不能少于6位。3、手机号码必须符合格式。

// index.vue
<template>
<form action="" method="post" ref="registerForm">
  请输入用户名:<input type="text" name="userName">
  请输入密码:<input type="text" name="password">
  请输入手机号码:<input type="text" name="phoneNumber">
  <button>提交</button>
</form>
</template>

<script lang="ts">
import { Validator } from '@/utils/validator'
import { defineComponent, onMounted, ref } from 'vue'
export default defineComponent({
  setup() {
    const registerForm = ref(null)
    const validateFunc = (form: any) => {
      const validate = new Validator();
      validate.add(form.userName, [{
        strategy: 'isNonEmpty',
        errorMsg: '用户名不能为空'
      },{
        strategy: 'minLength:10',
        errorMsg: '用户名不能少于10位'
      }])
      validate.add(form.password, [{
        strategy: 'minLength:6',
        errorMsg: '密码不能少于6位'
      }])
      validate.add(form.phoneNumber, [{
        strategy: 'isMobile',
        errorMsg: '手机号码格式不正确'
      }])
      const errorMsg = validate.start();
      return errorMsg
    }
    onMounted(() => {
      const form = registerForm.value;
      form.onsubmit = function(e) {
        e.preventDefault();
        const msg = validateFunc(form)
        if (msg) alert(msg)
      }
    })
    return{
      registerForm
    }
  },
})
</script>
// validator.js
// 策略对象
const strategies = {
  isNonEmpty(value, msg) {
    if (value === '') return msg;
  },
  minLength(value, length, msg) {
    if (value.length < length) return msg
  },
  isMobile(value, msg) {
    if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) return msg
  }
}
// Context类
export class Validator {
  constructor() {
    this.cache = []
  }
  add(dom, rules) {
    for (let i = 0; i < rules.length; i++) {
      const rule = rules[i]
      const ary = rule.strategy.split(':')
      const msg = rule.errorMsg
      this.cache.push(()=> {
        const strategy = ary.shift()
        ary.unshift(dom.value)
        ary.push(msg)
        return strategies[strategy].apply(dom, ary)
      })
    }
  }
  start() {
    for (let i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
      var msg = validatorFunc()
      if (msg) return msg
    }
  }
}

优点

  • 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句
  • 策略模式提供了开放-封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换,易于理解,易于扩展
  • 策略模式中的算法也可以复用在系统的其他地方,从而避免需要重复的复制粘贴工作
  • 在策略模式中利用组合和委托让Context拥有执行算法的能力,这也是继承的一种更轻便的替代方案