首页 > 编程语言 >JavaScript版本的策略模式

JavaScript版本的策略模式

时间:2023-06-21 13:04:42浏览次数:38  
标签:salary function return 策略 calculateBonus JavaScript 模式 算法 版本


俗话说,条条大路通罗马。在美剧《越狱》中,主角Michael Scofield就设计了两条越狱的道路。这两条道路都可以到达靠近监狱外墙的医务室。

同样,在现实中,很多时候也有多种途径到达同一个目的地。比如我们要去某个地方旅游,可以根据具体的实际情况来选择出行的线路。

  • 如果没有时间但是不在乎钱,可以选择坐飞机。
  • 如果没有钱,可以选择坐大巴或者火车。
  • 如果再穷一点,可以选择骑自行车。

JavaScript版本的策略模式_javascript


在程序设计中,我们也常常遇到类似的情况,要实现某一个功能有多种方案可以选择。比如一个压缩文件的程序,既可以选择zip算法,也可以选择gzip算法。

这些算法灵活多样,而且可以随意互相替换。这种解决方案就是本章将要介绍的策略模式。

策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

策略模式有着广泛的应用。本节我们就以年终奖的计算为例进行介绍。

很多公司的年终奖是根据员工的工资基数和年底绩效情况来发放的。例如,绩效为S的人年终奖有4倍工资,绩效为A的人年终奖有3倍工资,而绩效为B的人年终奖是2倍工资。假设财务部要求我们提供一段代码,来方便他们计算员工的年终奖。

1. 最初的代码实现

我们可以编写一个名为calculateBonus的函数来计算每个人的奖金数额。很显然,calculateBonus函数要正确工作,就需要接收两个参数:员工的工资数额和他的绩效考核等级。代码如下:

var calculateBonus = function( performanceLevel, salary ){

    if ( performanceLevel === 'S' ){
        return salary * 4;
    }

    if ( performanceLevel === 'A' ){
        return salary * 3;
    }

    if ( performanceLevel === 'B' ){
        return salary * 2;
    }

};

calculateBonus( 'B', 20000  );        // 输出:40000
calculateBonus( 'S', 6000 );        // 输出:24000

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

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

因此,我们需要重构这段代码。

2. 使用组合函数重构代码

一般最容易想到的办法就是使用组合函数来重构它,我们把各种算法封装到一个个的小函数里面,这些小函数有着良好的命名,可以一目了然地知道它对应着哪种算法,它们也可以被复用在程序的其他地方。代码如下:

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

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

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

var calculateBonus = function( performanceLevel, salary ){

    if ( performanceLevel === 'S' ){
        return performanceS( salary );
    }

    if ( performanceLevel === 'A' ){
        return performanceA( salary );
    }

    if ( performanceLevel === 'B' ){
        return performanceB( salary );
    }

};

calculateBonus(  'A' , 10000 );    // 输出:30000

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

3. 使用策略模式重构代码

经过思考,我们想到了更好的办法——使用策略模式来重构代码。策略模式指的是定义一系列的算法,把它们一个个封装起来。将不变的部分和变化的部分隔开是每个设计模式的主题,策略模式也不例外,策略模式的目的就是将算法的使用与算法的实现分离开来。

在这个例子里,算法的使用方式是不变的,都是根据某个算法取得计算后的奖金数额。而算法的实现是各异和变化的,每种绩效对应着不同的计算规则。

一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。 第二个部分是环境类Context,Context接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明Context中要维持对某个策略对象的引用。

现在用策略模式来重构上面的代码。第一个版本是模仿传统面向对象语言中的实现。我们先把每种绩效的计算规则都封装在对应的策略类里面:

var performanceS = function(){};

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

var performanceA = function(){};

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

var performanceB = function(){};

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

接下来定义奖金类Bonus

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 );    //把计算奖金的操作委托给对应的策略对象
};

在完成最终的代码之前,我们再来回顾一下策略模式的思想:

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换1

这句话如果说得更详细一点,就是:定义一系列的算法,把它们各自封装成策略类,算法被封装在策略类内部的方法里。在客户对Context发起请求的时候,Context总是把请求委托给这些策略对象中间的某一个进行计算。

1. “并且使它们可以相互替换”,这句话在很大程度上是相对于静态类型语言而言的。因为静态类型语言中有类型检查机制,所以各个策略类需要实现同样的接口。当它们的真正类型被隐藏在接口后面时,它们才能被相互替换。而在JavaScript这种“类型模糊”的语言中没有这种困扰,任何对象都可以被替换使用。因此,JavaScript中的“可以相互替换使用”表现为它们具有相同的目标和意图。

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

var bonus = new Bonus();

bonus.setSalary( 10000 );
bonus.setStrategy( new performanceS() );  //设置策略对象

console.log( bonus.getBonus() );    // 输出:40000    

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

刚刚我们用策略模式重构了这段计算年终奖的代码,可以看到通过策略模式重构之后,代码变得更加清晰,各个类的职责更加鲜明。但这段代码是基于传统面向对象语言的模仿,下一节我们将了解用JavaScript实现的策略模式。

在5.1节中,我们让strategy对象从各个策略类中创建而来,这是模拟一些传统面向对象语言的实现。实际上在JavaScript语言中,函数也是对象,所以更简单和直接的做法是把strategy直接定义为函数:

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

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

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

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

console.log( calculateBonus( 'S', 20000 ) );        // 输出: 80000
console.log( calculateBonus( 'A', 10000 ) );        // 输出: 30000

在接下来的缓动动画和表单验证的例子中,我们用到的都是这种函数形式的策略对象。

本文摘自《JavaScript设计模式与开发实践》


标签:salary,function,return,策略,calculateBonus,JavaScript,模式,算法,版本
From: https://blog.51cto.com/u_15767091/6528192

相关文章

  • JavaScript王国里的鸭子合唱团
    编程语言按照数据类型大体可以分为两类,一类是静态类型语言,另一类是动态类型语言。静态类型语言在编译时便已确定变量的类型,而动态类型语言的变量类型要到程序运行的时候,待变量被赋予某个值之后,才会具有某种类型。静态类型语言的优点首先是在编译时就能发现类型不匹配的错误,编辑......
  • 关于在Redhat-7-linux-系统-Apache-2.4.6-版本上部署多个版本的yum仓库-的配置文件写
    背景:云上有一台内部yum服务器,操作系统及版本信息为:RedHatEnterpriseLinuxServerrelease7.9(Maipo)上面每天会同aws仓库官网同步repo,版本也自然是 RedHatEnterpriseLinuxServerrelease7现在需要临时增加Redhat8.的仓库,(默认Redhat8也是有内部repo仓库的,只是在......
  • 10个具体项目生动精彩讲述JavaScript;超级Web应用,构建不再困难
     “JavaScript,就是那种小时候长得很丑,长大了却谁都想要的孩子。”诞生初期,由于很多所谓的“资深”程序员的滥用,让这个孩子饱受质疑,直到前几年DOM技术开始崛起,JavaScript才逐渐恢复了曾经的兴盛。并且,这时的JavaScript更多了一份成熟,少了一缕稚气。JavaScript虽然已经被当......
  • 活学活用流行的JavaScript库——《JavaScript实战》
    媒体评论“本书并不像一本教材,畅读之后,你会觉得它就是一位讲课生动的老师,带领你遨游JavaScript海洋,把你推向技术风浪的前沿,同时又给了你一个功能完备的冲浪板,接下来就是你在风口浪尖上享受JavaScript高潮的时候了。”                  ......
  • 单标签下的日间/黑夜模式切换按钮效果
    前不久,在网上看到这么一张非常有趣的图:想必很多同学都看到这张图,是一个开发小哥被一个日间/黑夜模式切换按钮效果逼疯的视频。其最终效果大致如下:原完整代码在这里:Night&&DayToggle☀️/......
  • Mysql数据库5.6版本安装
    5.6的软件包创建管理组mysql,创建用户解压·mysql的5.6版本移动到指定位置修改目录所有者优化mysql命令检查mysql版本修改主配置文件初始化mysql生成mysql服务控制文件添加到系统文件设置开机自启动启动mysqld并查看状态设置登录数据库密码登录数据库创建数据库查看数据库创建数据库......
  • 组合模式
    #include<iostream>#include<list>#include<string>usingnamespacestd;//componentclassIFile{public:virtualvoiddisplaye()=0;virtualintadd(IFile*iFile)=0;virtualintremove(IFile*iFile)=0;virtuall......
  • 三菱伺服定长追剪,系统为Q172DSCPU,高级同步模式。 包
    三菱伺服定长追剪,系统为Q172DSCPU,高级同步模式。包含一个程序例子,有详细的机械参数分析,伺服参数设置,以及追剪凸轮表设置的由来。YID:69100609575345770......
  • 单例模式
    1.将构造函数私有化2.在类中定义一个静态的指向本类型的指针变量3.定义一个返回值为类指针的静态成员函数四种单例模式的实现1.全局类+单例类(全局类负责回收单例类对象堆空间)#include<iostream>usingnamespacestd;classsingleinstance{public:staticsing......
  • 设计模式之工厂模式
    工厂模式是一种对象创建型模式,它提供了一种创建对象的最佳实践。在工厂模式中,我们在创建对象时不使用new关键字,而是通过调用工厂方法来创建对象。工厂方法是一种在子类中定义的方法,该方法负责实例化对象。工厂方法可以返回不同的对象类型,因此工厂模式可以创建一组相关或不相关的......