Maoyl's blog Maoyl's blog
首页
  • 前端基础

    • HTML
    • CSS
    • CSS动画
    • JavaScript文章
    • stylus
  • 性能优化

    • 《性能优化》笔记
  • 学习笔记

    • 《JavaScript教程》笔记
    • 《JavaScript高级程序设计》笔记
    • 《ES6 教程》笔记
    • 《JavaScript设计模式》笔记
    • 《TypeScript 从零实现 axios》
    • TypeScript笔记
    • JS设计模式总结笔记
  • 前端框架

    • Vue相关
    • React相关
  • 前端监控

    • 前端监控简介
  • 学习笔记

    • 《Vue》笔记
    • 《React》笔记
    • 小程序学习笔记
  • 后端基础

    • Nodejs
  • 学习笔记

    • 数据结构
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • 网页性能
  • 学习笔记

    • 《Git》学习笔记
    • 《Vim》学习笔记
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

maoyln

日日行,不怕千万里
首页
  • 前端基础

    • HTML
    • CSS
    • CSS动画
    • JavaScript文章
    • stylus
  • 性能优化

    • 《性能优化》笔记
  • 学习笔记

    • 《JavaScript教程》笔记
    • 《JavaScript高级程序设计》笔记
    • 《ES6 教程》笔记
    • 《JavaScript设计模式》笔记
    • 《TypeScript 从零实现 axios》
    • TypeScript笔记
    • JS设计模式总结笔记
  • 前端框架

    • Vue相关
    • React相关
  • 前端监控

    • 前端监控简介
  • 学习笔记

    • 《Vue》笔记
    • 《React》笔记
    • 小程序学习笔记
  • 后端基础

    • Nodejs
  • 学习笔记

    • 数据结构
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • 网页性能
  • 学习笔记

    • 《Git》学习笔记
    • 《Vim》学习笔记
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JS设计模式总结笔记
  • 单例模式
  • 代理模式
  • 事件代理(事件委托)
  • 策略模式
    • 策略模式
      • 什么是策略模式
      • 使用策略模式计算绩效奖金
      • 拓展
  • 《JavaScript设计模式》笔记
maoyln
2022-08-29
目录

策略模式

# 策略模式

# 什么是策略模式

策略模式是一种行为设计模式,定义一系列算法,将每一个算法封装起来,并让它们可以相互替换;

策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy);

# 使用策略模式计算绩效奖金

很多公司的年终跟绩效挂钩:绩效为S的是2倍工资、绩效为A的为1.5倍工资、绩效为B的为1.2倍工资; 假设我们的财务需要一段代码方便计算年终奖;

# 最初的代码

我们可以通过编写一个名为calculateBonus的函数来计算每一个人的奖金数额; 很显然calculateBonus函数要正常工作责需要接受两个参数:基本工资和和绩效考核等级;

代码如下:

const calculateBonus = function(level, salary) {
    if (level === 'S') {
        return salary * 2;
    }
    if (level === 'A') {
        return salary * 1.5;
    }
    if (level === 'B') {
        return salary * 1.2;
    }
}

calculateBonus('S', 1000);
// 输出 2000
calculateBonus('A', 1000);
// 输出 1500
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

可以发现,这段代码很简单,但是存在的显而易见的缺点

  • calculateBonus函数比较庞大,包含了很多if-else语句,这些语句需要覆盖所有的逻辑分支;
  • calculateBonus函数缺乏弹性,如果增加了一种新的绩效等级C,或则想把绩效为S的奖金系数改为2.5,那么我们必须深入calculateBonus函数内部的实现,这是违反开放-封闭原则的;
  • 算法的复用逻辑性差,如果在程序的其他地方需要重用这些计算逻辑,我们只能ctrl+c和ctrl+v;

开放-封闭原则: 对拓展开放、对修改关闭,用最少的代码,甚至不用代码修改原来的逻辑,只要添加新的扩展逻辑代码;

因此我们需要重构一下代码;

# 一轮修改后代码

const performanceS = function (salary) {
    return salary * 2;
}

const performanceA = function (salary) {
    return salary * 1.5;
}

const performanceB = function (salary) {
    return salary * 1.2;
}

const calculateBonus = function (level salary) {
    if (level === 'S') {
        return performanceS(salary);
    }
    if (level === 'A') {
        return performanceA(salary);
    }
    if (level === 'B') {
        return performanceB(salary);
    }
}

calculateBonus('S', 1000);
// 输出 2000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

目前我们的程序得到了一定的改善,但是这种改善非常有限,我们依然没有解决最重要的问题:calculateBonus函数可能越来越庞大,而且在系统变化的时候缺乏弹性;

其实使用这些 if-else 的目的就是为了对应状态和计算方式。

# 使用策略模式重构代码

因为该文章写的是策略模式,显而易见,我们将要用策略模式对上述代码进行重构;

将不变的和变化的部分隔开是每一个设计模式的主题,策略模式也不例外; 策略模式的目的就是将算法的使用和算法的实现分离开来;

在这里例子里:算法的使用方式是不变的,而算法的实现是变化的;

一个基于策略模式的程序,必须有两部分组成: 第一部分:是一组策略类,策略类中封装了具体的算法过程 第二部分:环境类Content,它可以接受客户请求,然后把请求委托给策略类;

模拟面向对象例子

策略对象:

    const performanceS  = function () {}
    performanceS.prototype.calculate = function (salary) {
        return salary * 2;
    }

    const performanceA  = function () {}
    performanceA.prototype.calculate = function (salary) {
        return salary * 1.5;
    }

    const performanceB  = function () {}
    performanceB.prototype.calculate = function (salary) {
        return salary * 1.2;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

下面定义Bonus:

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

    Bonus.prototype.setSalary = function (salary) {
        this.salary = salary; // 设置原始工资
    }
    Bonus.prototype.setStringify = function (stringify) {
        this.stringify = stringify; // 设置员工绩效等级对应的策略对象
    }

    Bonus.prototype.getBonus = function () {
        return this.stringify.calculate(this.salary); // 把计算奖金的操作委托给策略对象
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

使用:

    const bonus = new Bonus();
    bonus.setSalary(1000);
    bonus.setStringify(new performanceS); // 设置策略对象

    console.log(bonus.getBonus()); // 输出 2000

    bonus.setStringify(new performanceA); // 设置策略对象
    console.log(bonus.getBonus()); // 输出 1500

1
2
3
4
5
6
7
8
9

上述代码,看着比较繁琐对吧,因为上面代码是基于传统面向对象语言的模仿; 实际在javascript语言中,函数也是对象,所以我们有更简单和直接的方法是把strategy直接定义为函数;

const strategies =  {
    'S': function(salary) {
        return salary * 2;
    },
    'A': function(salary) {
        return salary * 1.5;
    },
    'B': function(salary) {
        return salary * 1.2;
    },
};
1
2
3
4
5
6
7
8
9
10
11

同样,Context也没有必要用到Bonus类来表示,我们依然用calculateBonus函数充当Context来接受用户请求; 经过改造,代码结构变得更加简洁:

    const calculateBonus = function(level, salary) {
        return strategies[level](salary);
    }

    console.log(calculateBonus('S', 1000)); // 输出2000
1
2
3
4
5

es6类实现

//接下来定义奖金类Bonus:
class Bonus {
  constructor() {
    this.salary = null; // 原始工资
    this.strategy = null; // 绩效等级对应的策略对象
  }
  setSalary(salary) {
    this.salary = salary; // 设置员工的原始工资
  }
  setStrategy(strategy) {
    this.strategy = strategy; // 设置员工绩效等级对应的策略对象
  }
  getBonus() { // 取得奖金数额
    return this.strategy.calculate(this.salary); // 把计算奖金的操作委托给对应的策略对象
  }
}

const bonus = new Bonus();
bonus.setSalary(1000);

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 总结

看到这里,我们对策略模式有了大致了解,下面我们总结一下,

# 流程图比较
  • 之前的代码逻辑

  • 优化后的代码逻辑

以上的优化策略就是使用了设计模式之策略模式,在实际的项目开发过程中还是比较实用。

# 优缺点

优点

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

缺点

  • 增加许多策略类或者策略对象,但实际上这比把它们负责的 逻辑堆砌在 Context 中要好。
  • 要使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点, 这样才能选择一个合适的 strategy。
# 使用场景

在什么情况下可以考虑使用策略模式呢?如果函数具有以下特征:

  • 判断条件很多
  • 各个判断条件下的代码相互独立

然后可以将每个判断条件下的代码封装成一个独立的函数,然后建立判断条件和具体策略的映射关系。

# 拓展

# 多态在策略模式中的体现

通过使用策略模式的重构代码,我们消除了原程序中大片的条件分支语句; 所有跟计算逻辑不相关的不放在Context中,而是分布在各个策略对象中; Context并没有计算能力,而是把这个职责委托给某个策略对象; 每个策略对象负责的算法已被各个自封在对象内部; 当我们对这些策略对象发出计算奖金的请求时,他们会返回各自不同的结果; 这正是多态的体现,也是他们可以相互替换的目的; 替换Content中当前保存的策略对象,便能执行不同的算法来得到我们想要的结果;

# 小事例(出自《JavaScript设计模式与开发实践》)

# 缓动动画

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

分析: 首先缓动算法的职责是实现小球如何运动 然后动画类(即context)的职责是负责:

  • 初始化动画对象
    • 在运动开始之前,需要提前记录一些有用的信息,至少包括以下信息:

      • 动画开始时的准确时间点;
      • 动画开始时,小球所在的原始位置;
      • 小球移动的目标位置;
      • 小球运动持续的时间。
  • 计算小球某时刻的位置
  • 更新小球的位置
<html>
    <div class="demo-container">
    <div class="box" id="div">
        <div class="ring1"></div>
        <div class="ring2"></div>
    </div>
    <script>
    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;
        }
    };

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


    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);
        }
        }, 16);
    };

    Animate.prototype.step = function () {
        var t = +new Date; // 取得当前时间
        if (t >= this.startTime + this.duration) { // (1)
        this.update(this.endPos); // 更新小球的CSS 属性值
        return false;
        }
        var pos = this.easing(t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration);
        // pos 为小球当前位置
        this.update(pos); // 更新小球的CSS 属性值
    };

    Animate.prototype.update = function (pos) {
        this.dom.style[this.propertyName] = pos + 'px';
    };

    var div = document.getElementById('div');
    var animate = new Animate(div);
    setInterval(() => {
        // animate.start('left', 0, 1500, 'linear');
        //   animate.start( 'left', 0, 1500, 'easeIn');
        //   animate.start( 'left', 0, 1500, 'strongEaseIn');
        //   animate.start( 'left', 0, 1500, 'strongEaseOut');
        //   animate.start( 'left', 0, 1500, 'sineaseIn');
          animate.start( 'left', 0, 1500, 'sineaseOut');
    }, 1600);
    </script>
    <style>
        .demo-container {
            width:30vw;
            height:20px;          
        }
  
        .box {
            position:absolute;
        }

        .ring1 {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 16px;
            height: 16px;
            border-radius: 50%;
            animation: mark-ring1-animation 2s infinite;
            background-color: rgb(255, 159, 0)
        }
        .ring2 {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 32px;
            height: 32px;
            border-radius: 50%;
            animation: mark-ring2-animation 2s infinite;  
            background-color: rgb(255, 159, 0)
        }
        @keyframes mark-ring1-animation {
            0% {
                width: 16px;
                height: 16px;
                opacity: 1;
            }

            100% {
                width: 32px;
                height: 32px;
                opacity: 0.5;
            }
        }
        @keyframes mark-ring2-animation {
            0% {
                width: 32px;
                height: 32px;
                opacity: 0.5;
            }

            100% {
                width: 50px;
                height: 50px;
                opacity: 0.3;
            }
        }
    </style>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
编辑 (opens new window)
#设计模式#JavaScript
上次更新: 2025/04/18, 01:42:12
事件代理(事件委托)

← 事件代理(事件委托)

最近更新
01
GSAP动画库——如何高效写动画
04-17
02
自适应方案PxToRem
09-10
03
性能优化-requestAnimationFrame
08-10
更多文章>
Theme by Vdoing | Copyright © 2019-2025 备案号:京ICP备19058102号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式