【设计模式】策略模式——以商场促销为例
2015-12-04 14:25
477 查看
本文内容参考自《大话设计模式》(程杰 著)
注:以下代码为java实现
关键代码:
思路1:修改代码,比如打7折,则total *= 0.7;
评价:如果取消打折,或者修改折扣,需要频繁修改代码,不推荐。
思路2:增加折扣选项,关键代码如下:
问题:重复的代码太多,而且选项少,可变性不高!
以上代码进行了抽象封装,并使用了简单的工厂类,灵活性高了很多。
问题:简单工厂模式只是解决对象的创建问题,而且由于工厂本身包括了所有的收费方式,商场是可能经常性地更改打折额度和返利额度的,每次维护或扩展收费方式都要改动这个工厂,以致代码需要重新编译部署,这是非常糟糕的处理方式,所以用它不是最好的办法。面对算法的时常变动,我们可以使用策略模式!
商场收银时,如何促销,用打折还是返利,其实都是一些算法,最重要的是这些算法是随时都可能互相替换的,就这点变化,而封装变化点是我们面向对象的一种很重要的思维方式。我们来看看策略模式的结构图和基本代码:
所以我们的可以进行以下修改:
问题:缺乏工厂模式的优势,在客户端要进行判断。
简单工厂模式需要让客户端认识两个类,CashSuper和CashFactory,而策略模式与简单工厂模式结合的用法,客户端就只需要认识一个类CashContext。耦合度更加降低。
我们在客户端实例化的是CashContext的对象,调用的是CashContext的方法,这使得具体的收费算法彻底地与客户端分离。连算法的父类CashSuper都不让客户端认识了。相当于创建了一个句柄类。
另外,它简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
遗留问题:如果我们需要增加一种算法,比如满200返50,你就必须要改CashContext中的if或switch代码,有没有更低的维护成本?
(使用反射)
注:以下代码为java实现
版本1
需求:
做一个商场收银软件,营业员根据客户所购买商品的单价和数量,向客户收费。关键代码:
public class Cash { private double total = 0; public void submit(int num, double price) { double totalPrices = num * price; total += totalPrices; System.out.println("单价:" + price + " 数量:" + num + "合计:" + totalPrices); } public double getTotal() { return total; } public void setTotal(double total) { this.total = total; } }
版本2
需求:
增加打折功能思路1:修改代码,比如打7折,则total *= 0.7;
评价:如果取消打折,或者修改折扣,需要频繁修改代码,不推荐。
思路2:增加折扣选项,关键代码如下:
public class Cash { private double total = 0; private int selectedIndex = 0; public void selectFormLoad() { String[] selectForm = { "正常收费", "打8折", "打7折", "打5折" }; selectedIndex = 0; } public void submit(int num, double price) { double totalPrices = 0; switch (selectedIndex) { case 0: totalPrices = num * price; break; case 1: totalPrices = num * price * 0.8; break; case 2: totalPrices = num * price * 0.7; break; case 3: totalPrices = num * price * 0.5; break; } total += totalPrices; System.out.println("单价:" + price + " 数量:" + num + "合计:" + totalPrices); } public double getTotal() { return total; } public void setTotal(double total) { this.total = total; } public int getSelectedIndex() { return selectedIndex; } public void setSelectedIndex(int selectedIndex) { this.selectedIndex = selectedIndex; } }
问题:重复的代码太多,而且选项少,可变性不高!
版本3
需求:
可以灵活修改折扣,并且可以返利//现金收费接口 public interface CashSuper { public double acceptCash(double money); } //正常收费子类 public class CashNormal implements CashSuper { public double acceptCash(double money) { return money; } } //打折收费子类 public class CashRebate implements CashSuper { private double moneyRebate = 1; public CashRebate(double moneyRebate) { this.moneyRebate = moneyRebate; } public double acceptCash(double money) { return money * moneyRebate; } } //返利收费子类 public class CashReturn implements CashSuper { private double moneyCondition = 0; private double moneyReturn = 0; public CashReturn(double moneyCondition, double moneyReturn) { this.moneyCondition = moneyCondition; this.moneyReturn = moneyReturn; } public double acceptCash(double money) { double result = money; if (money >= moneyCondition) { result = money - money / moneyCondition * moneyReturn; } return result; } } //现金收费工厂类 public class CashFactory { public static CashSuper createCash(String type) { CashSuper cs = null; if ("正常收费".equals(type)) { cs = new CashNormal(); } else if ("满300返100".equals(type)) { cs = new CashReturn(300, 100); } else if ("打8折".equals(type)) { cs = new CashRebate(0.8); } return cs; } } //客户端代码 public class Main { private static double total = 0; public static void main(String[] args) { consume("正常收费", 1, 1000); consume("满300返100", 1, 1000); consume("打8折", 1, 1000); System.out.println("总计:" + total); } public static void consume(String type, int num, double price) { CashSuper csuper = CashFactory.createCash(type); double totalPrices = 0; totalPrices = csuper.acceptCash(num * price); total += totalPrices; System.out.println("单价:" + price + " 数量:" + num + "合计:" + totalPrices); } }
以上代码进行了抽象封装,并使用了简单的工厂类,灵活性高了很多。
问题:简单工厂模式只是解决对象的创建问题,而且由于工厂本身包括了所有的收费方式,商场是可能经常性地更改打折额度和返利额度的,每次维护或扩展收费方式都要改动这个工厂,以致代码需要重新编译部署,这是非常糟糕的处理方式,所以用它不是最好的办法。面对算法的时常变动,我们可以使用策略模式!
版本4
需求:
可以经常性地更改打折额度和返利额度,而且要维护成本较低。策略模式(Strategy):
它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。商场收银时,如何促销,用打折还是返利,其实都是一些算法,最重要的是这些算法是随时都可能互相替换的,就这点变化,而封装变化点是我们面向对象的一种很重要的思维方式。我们来看看策略模式的结构图和基本代码:
//Strategy类,定义所有支持的算法的公共接口 public interface Strategy { public void algorithmInterface(); } //ConcreteStrategy封装了具体的算法或行为,继承于Strategy public class ConcreteStrategyA implements Strategy { public void algorithmInterface() { System.out.println("算法A实现"); } } public class ConcreteStrategyB implements Strategy { public void algorithmInterface() { System.out.println("算法A实现"); } } public class ConcreteStrategyC implements Strategy { public void algorithmInterface() { System.out.println("算法C实现"); } } //Context用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用 public class Context { private Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public void contextInterface() { strategy.algorithmInterface(); } } //客户端代码 public class Main { public static void main(String[] args) { Context context; context = new Context(new ConcreteStrategyA()); context.contextInterface(); context = new Context(new ConcreteStrategyB()); context.contextInterface(); context = new Context(new ConcreteStrategyC()); context.contextInterface(); } }
所以我们的可以进行以下修改:
//CashContext类 public class CashContext { CashSuper cashSuper; public CashContext(CashSuper cashSuper) { this.cashSuper = cashSuper; } public double acceptCash(double money) { return cashSuper.acceptCash(money); } } //客户端代码 public class Main { private static double total = 0; public static void main(String[] args) { consume("正常收费", 1, 1000); consume("满300返100", 1, 1000); consume("打8折", 1, 1000); System.out.println("总计:" + total); } public static void consume(String type, int num, double price) { CashContext cashContext = null; if ("正常收费".equals(type)) { cashContext = new CashContext(new CashNormal()); } else if ("满300返100".equals(type)) { cashContext = new CashContext(new CashReturn(300, 100)); } else if ("打8折".equals(type)) { cashContext = new CashContext(new CashRebate(0.8)); } double totalPrices = cashContext.acceptCash(num * price); total += totalPrices; System.out.println("单价:" + price + " 数量:" + num + "合计:" + totalPrices); } }
问题:缺乏工厂模式的优势,在客户端要进行判断。
版本5
将策略模式与简单工厂模式结合起来://改造后的CashContext public class CashContext { CashSuper cashSuper; public CashContext(CashSuper cashSuper) { this.cashSuper = cashSuper; } public CashContext(String type) { if ("正常收费".equals(type)) { cashSuper = new CashNormal(); } else if ("满300返100".equals(type)) { cashSuper = new CashReturn(300, 100); } else if ("打8折".equals(type)) { cashSuper = new CashRebate(0.8); } } public double acceptCash(double money) { return cashSuper.acceptCash(money); } } //客户端代码 public class Main { private static double total = 0; public static void main(String[] args) { consume("正常收费", 1, 1000); consume("满300返100", 1, 1000); consume("打8折", 1, 1000); System.out.println("总计:" + total); } public static void consume(String type, int num, double price) { CashContext cashContext = new CashContext(type); double totalPrices = cashContext.acceptCash(num * price); total += totalPrices; System.out.println("单价:" + price + " 数量:" + num + "合计:" + totalPrices); } }
简单工厂 和 策略模式+简单工厂 的对比
客户端代码://简单工厂模式的用法 CashSuper csuper = CashFactory.createCash(type); ... totalPrices = csuper.acceptCash(num * price); //策略模式与简单工厂模式结合的用法 CashContext cashContext = new CashContext(type); double totalPrices = cashContext.acceptCash(num * price);
简单工厂模式需要让客户端认识两个类,CashSuper和CashFactory,而策略模式与简单工厂模式结合的用法,客户端就只需要认识一个类CashContext。耦合度更加降低。
我们在客户端实例化的是CashContext的对象,调用的是CashContext的方法,这使得具体的收费算法彻底地与客户端分离。连算法的父类CashSuper都不让客户端认识了。相当于创建了一个句柄类。
总结
策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法与使用算法之间的耦合。另外,它简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
遗留问题:如果我们需要增加一种算法,比如满200返50,你就必须要改CashContext中的if或switch代码,有没有更低的维护成本?
(使用反射)
相关文章推荐
- 正则表达式之awk学习笔记
- JavaScript encodeURI 和encodeURIComponent
- 分享+ 在根视图scrollView里面添加子视图
- Failed to create the VirtualBoxClient COM object. 解决这个鸟问题!
- NPOI2.2.0.0实例详解(二)—使用NPOI创建EXCEL
- 详解JavaScript逻辑Not运算符
- Linux进程实时IO监控iotop命令
- codeforces-346A-Alice and Bob【数论】
- UI视图控件、视图嵌套、SubView、Tag 的使用
- shell 删除文件下的* (copy).jpg备份文件
- 两台Mysql数据库数据同步实现
- MySQL ORDER BY 的实现分析
- Swift! Swift! Swift! 重要的事情说3遍!
- Android UICC 实现框架和数据读写
- postgresql启用日志记录
- 正则表达式之sed学习笔记
- liferay学习博文链接
- Hadoop RPC协议之 ProtobufRpcEngine
- 剑指offer系列之六:旋转数组的最小值
- POI 基于JAVA的通用类抽取