策略模式
官方定义:定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。
个人理解:选择执行多个规则中的某个规则。
需求 1: 开发一个商场收银系统 v1.0
三下五除二搞定
代码实现:
- var price = Convert.ToDouble(txtPrice.Text);//单价
- var number = Convert.ToDouble(txtNumber.Text);//数量
- var lastTotal = Convert.ToDouble(labTotal.Text);//已购买金额
- var money = price * number;//本次计算金额
- labTotal.Text = (lastTotal + money).ToString();
- txtContent.Text += string.Format("单价:{0},数量:{1},金额:{2}", price, number, money + "\r\n");
系统简单、方便、实用、性能好。... 几个月过去,马上要国庆长假,老板为了促销决定全场 8.8 折。
需求 2: 加入打折功能
因为项目工期紧,三下五除二又搞定
- var price = Convert.ToDouble(txtPrice.Text);//单价
- var number = Convert.ToDouble(txtNumber.Text);//数量
- var lastTotal = Convert.ToDouble(labTotal.Text);//已购买金额
- var discount = 0.88;//折扣(新增代码)
- var money = price * number * discount;//本次计算金额
- labTotal.Text = (lastTotal + money).ToString();
- txtContent.Text += string.Format("单价:{0},数量:{1},折扣:{2},实际金额:{2}", price, number, discount, money + "\r\n");
很是自豪,我开发效率就是这么高。老板也甚是高兴。
... 转眼假期就要过去了,打折的活动也要取消了。但是,由于这次的促销效果收益还不错。老板决定继续打折活动,折扣率要成为 9.8 折,且只是部分商品。不打折的商品则实行满 300 返 40,满 600 返 100。
不对劲啊,又的改代码。到了明年是不是又要 8.8?老板的心思猜不透,但程序可以写得更灵活。所以我们要好好构思下。让系统可以选择优惠策略。
需求 3: 修改打折,并加入返现功能
代码实现如下
- var price = Convert.ToDouble(txtPrice.Text);//单价
- var number = Convert.ToDouble(txtNumber.Text);//数量
- var lastTotal = Convert.ToDouble(labTotal.Text);//已购买金额
- var money = price * number;//本次计算金额
- switch (cmBstrategy.Text)//下拉框
- {
- case "8.8折":
- money *= 0.88;
- break;
- case "9.8折":
- money *= 0.98;
- break;
- case "满300返40":
- if (money >= 300)
- {
- money -= 40;
- }
- break;
- case "满600返100":
- if (money >= 600)
- {
- money -= 100;
- }
- break;
- }
- labTotal.Text = (lastTotal + money).ToString();
- txtContent.Text += string.Format("单价:{0},数量:{1},促销:{2},实际金额:{3}", price, number, cmBstrategy.Text, money + "\r\n");
现在我们的收银员可以灵活切换优惠活动,且保留的原有的优惠策略。不过我们从代码层面考虑的话,还有多处不足。
根据面向对象的思想,应该封装变化。于是,我们的策略模式可以登场了。
代码重构 使用策略模式实现以上需求
- var price = Convert.ToDouble(txtPrice.Text);//单价
- var number = Convert.ToDouble(txtNumber.Text);//数量
- var lastTotal = Convert.ToDouble(labTotal.Text);//已购买金额
- var context = new Context(cmBstrategy.Text);//新增代码
- var money = context.Calculation(price, number);//新增代码
- labTotal.Text = (lastTotal + money).ToString();
- txtContent.Text += string.Format("单价:{0},数量:{1},促销:{2},实际金额:{3}", price, number, cmBstrategy.Text, money + "\r\n");
我们发现中间那段条件分支不见了,多了一个 Context 类。
- public class Context
- {
- //策略抽象类
- private AmountCalculation amountCalculation;
- public Context(string type)
- {
- switch (type)
- {
- case "8.8折":
- amountCalculation = new Rebate(0.88);
- break;
- case "9.8折":
- amountCalculation = new Rebate(0.98);
- break;
- case "满300返40":
- amountCalculation = new Cashback(300, 40);
- break;
- case "满600返100":
- amountCalculation = new Cashback(600, 100);
- break;
- }
- }
- //计算金额
- public double Calculation(double price, double number)
- {
- return amountCalculation.Calculation(price, number);
- }
- }
里面有类 Rebate 折扣计算、Cashback 返现计算。
- //折扣计算
- public class Rebate : AmountCalculation
- {
- private double discountRate;
- public Rebate(double discountRate)
- {
- this.discountRate = discountRate;
- }
- public override double Calculation(double price, double number)
- {
- return price * number * discountRate;
- }
- }
- // 返现
- public class Cashback : AmountCalculation
- {
- //满多少
- private double exceed;
- //返多少
- private double retreat;
- public Cashback(double exceed, double retreat)
- {
- this.exceed = exceed;
- this.retreat = retreat;
- }
- public override double Calculation(double price, double number)
- {
- var momoney = price * number;
- if (momoney >= exceed)
- {
- return momoney - retreat;
- }
- return momoney;
- }
- }
看到这里,是不是明白了策略模式呢?
如果现在老板再需要我们价格折扣或是返现的活动,相比之前需要在长段的界面逻辑代码里面修改,现在要方便得多了。
第一、先在界面添加一个活动如加一个 7.8 折,然后界面代码就不用动了。
第二、在 Context 类里面加一个 7.8 折
- switch (type)
- {
- //新增
- case "7.8折":
- amountCalculation = new Rebate(0.78);
- break;
上面用 C# 实现了策略模式,接下来我们尝试使用 js 来实现。还是借用用上面的商场活动业务。
js 不同于传统的面向对象,无类、不需要实现抽象类。
- //策略计算
- var strategies = {
- //返现 exceed:满多少 retreat:返多少 momoney:应付金额
- cashBack: function (exceed, retreat, momoney) {
- if (momoney >= exceed) {
- return (momoney - retreat).toFixed(2);
- }
- return momoney;//返现后实付金额
- },
- //打折 discountRate:折扣率 momoney:应付金额
- rebate: function (discountRate, momoney) {
- return (discountRate * momoney).toFixed(2);//折扣后实付金额
- }
- }
- //上下文
- var context = {
- "7.8折": function (price, number) {
- var momoney = price * number;
- return strategies.rebate(0.78, momoney);
- },
- "9.8折": function (price, number) {
- var momoney = price * number;
- return strategies.rebate(0.98, momoney);
- },
- "满300返40": function (price, number) {
- var momoney = price * number;
- return strategies.cashBack(300, 40, momoney);
- },
- "满600返100": function (price, number) {
- var momoney = price * number;
- return strategies.cashBack(600, 100, momoney);
- }
- }
- //计算结果
- var calculateBonus = function (level, price, number) {
- return context[level](price, number);
- };
- //调用
- console.log(calculateBonus('7.8折', 12, 3)); //计算
- console.log(calculateBonus('满600返100', 12, 3)); //计算
- console.log(calculateBonus('满300返40', 2, 23)); //计算
- console.log(calculateBonus('9.8折', 2, 33)); //计算
结果如下:
相对于面向对象语言的实现要更加的清晰明了。
那么 js 可以模拟面向对象的实现吗?答案是肯定的。
首先定义返现实现类:
- //返现 exceed:满多少 retreat:返多少
- var CashBack = function (exceed, retreat) {
- this.exceed = exceed;
- this.retreat = retreat;
- };
- //计算方法
- CashBack.prototype.calculate = function (price, number) {
- var momoney = price * number;
- if (momoney >= this.exceed) {
- return (momoney - this.retreat).toFixed(2);
- }
- return momoney;//返现后实付金额
- }
打折类
- //打折 discountRate:折扣率 momoney:应付金额
- var Rebate = function (discountRate) {
- this.discountRate = discountRate;
- };
- //计算方法
- Rebate.prototype.calculate = function (price, number) {
- return (price * number * this.discountRate).toFixed(2);//折扣后实付金额
- }
策略上下文
- //上下文
- var Context = function (type) {
- this.type = type;
- }
- Context.prototype.calculation = function (price, number) {
- var AmountCalculation;
- switch (this.type) {
- case "7.8折":
- AmountCalculation = new Rebate(0.78);
- break;
- case "9.8折":
- AmountCalculation = new Rebate(0.98);
- break;
- case "满300返40":
- AmountCalculation = new CashBack(300, 40);
- break;
- case "满600返100":
- AmountCalculation = new CashBack(600, 100);
- break;
- }
- return AmountCalculation.calculate(price, number);
- }
调用如下:
- //调用
- var context = new Context("7.8折");
- console.log(context.calculation(12, 3));
- var context = new Context("9.8折");
- console.log(context.calculation(12, 3)); //计算
- var context = new Context("满300返40");
- console.log(context.calculation(300, 2)); //计算
- var context = new Context("满600返100");
- console.log(context.calculation(300, 3)); //计算
虽然对于 js 语言特性直接实现策略模式来说,面向对象的代码量比较多。可是对于我们后端人员 new 一个对象的使用方式应该更习惯。
策略模式场景
总结
策略模式通过 Context 上下文对具体策略进行封装,供高层直接调用而不用关系策略的具体实现。然后 Context 本身通过不同情况实例不同的抽象实现类(具体策略类),来执行具体策略。从而实现了具体策略的自由切换,易于新策略的扩展。
本文已同步至索引目录:
本文 demo:
来源: http://www.cnblogs.com/zhaopei/p/strategy.html