经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
大话设计模式笔记(二)の 策略模式
来源:cnblogs  作者:callmeDevil  时间:2019/6/3 9:01:16  对本文有异议

前言

个人风格系列笔记,将《大话设计模式》中C#代码做Java实现,稍有修改,逻辑不会太详细,不喜勿喷,适用于复习设计模式而做此分享。

举个栗子

问题描述

商场收银软件,营业员根据客户所购买的商品单价和数量,向客户收费。

简单实现

  1. /**
  2. * 普通实现
  3. * Created by callmeDevil on 2019/6/1.
  4. */
  5. public class NormalTest {
  6. public static void main(String[] args) {
  7. double price = 10;
  8. double num = 5;
  9. System.out.println(String.format("单价:%s 元", price));
  10. System.out.println(String.format("数量:%s 个", num));
  11. System.out.println(String.format("总价:%s 元", calculateTotal(price, num)));
  12. }
  13. /**
  14. * 计算总价
  15. *
  16. * @param price 单价
  17. * @param num 数量
  18. * @return
  19. */
  20. private static double calculateTotal(double price, double num) {
  21. return price * num;
  22. }
  23. }

问题2

商品搞促销,打八折,也可能打七折,甚至五折。

数组实现

  1. /**
  2. * 普通实现2
  3. * Created by callmeDevil on 2019/6/1.
  4. */
  5. public class NormalTest2 {
  6. public static void main(String[] args) {
  7. double price = 10;
  8. double num = 5;
  9. String[] discounts = {"正常收费", "打八折", "打七折", "打五折"};
  10. System.out.println(String.format("单价:%s 元", price));
  11. System.out.println(String.format("数量:%s 个", num));
  12. System.out.println(String.format("折扣:%s ", discounts[1]));
  13. System.out.println(String.format("总价:%s 元", calculateTotal(price, num, 1)));
  14. }
  15. /**
  16. * 计算总价
  17. *
  18. * @param price 单价
  19. * @param num 数量
  20. * @param discount 折扣
  21. * @return
  22. */
  23. private static double calculateTotal(double price, double num, int discount) {
  24. double total = 0L;
  25. switch (discount) {
  26. case 0:
  27. total = price * num;
  28. break;
  29. case 1:
  30. total = price * num * 0.8;
  31. break;
  32. case 2:
  33. total = price * num * 0.7;
  34. break;
  35. case 3:
  36. total = price * num * 0.5;
  37. break;
  38. default:
  39. break;
  40. }
  41. return total;
  42. }
  43. }

上述方式存在问题

有很多重复代码,就switch语句来说,如果计算方式比较复杂,那么这里就会显得非常冗余,必须考虑重构,抽出共性代码。而且如果需要打其他折扣,修改的地方也很多。

使用简单工厂模式

面向对象的编程,并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。

  1. /**
  2. * 现金收费抽象类
  3. * Created by callmeDevil on 2019/6/1.
  4. */
  5. public abstract class CashSuper {
  6. /**
  7. * 收取现金
  8. *
  9. * @param money 原价
  10. * @return 当前价
  11. */
  12. public abstract double acceptCash(double money);
  13. }
  1. /**
  2. * 正常收费子类
  3. * Created by callmeDevil on 2019/6/1.
  4. */
  5. public class CashNormal extends CashSuper {
  6. @Override
  7. public double acceptCash(double money) {
  8. // 正常收费,原价返回
  9. return money;
  10. }
  11. }
  1. /**
  2. * 返利收费子类
  3. * Created by callmeDevil on 2019/6/1.
  4. */
  5. public class CashReturn extends CashSuper{
  6. // 返利条件
  7. private double moneyCondition = 0;
  8. // 返利值
  9. private double moneyReturn = 0;
  10. // 返利收费,初始化时必须输入返利条件和返利值,比如满300返100,
  11. // 则moneyCondition 为300,moneyReturn 为100
  12. public CashReturn(double moneyCondition, double moneyReturn) {
  13. this.moneyCondition = moneyCondition;
  14. this.moneyReturn = moneyReturn;
  15. }
  16. @Override
  17. public double acceptCash(double money) {
  18. double result = money;
  19. if (money >= moneyCondition) {
  20. // 若大于返利条件,则需要减去返利值
  21. result = money - Math.floor(money / moneyCondition) * moneyReturn;
  22. }
  23. return result;
  24. }
  25. }
  1. /**
  2. * 打折收费子类
  3. * Created by callmeDevil on 2019/6/1.
  4. */
  5. public class CashRebate extends CashSuper{
  6. // 折扣率
  7. private double moneyRebate = 1;
  8. public CashRebate(double moneyRebate) {
  9. // 打折收费,初始化时,必须输入折扣率,如打八折,就是0.8
  10. this.moneyRebate = moneyRebate;
  11. }
  12. @Override
  13. public double acceptCash(double money) {
  14. return money * moneyRebate;
  15. }
  16. }
  1. /**
  2. * 现金收费工厂类
  3. * Created by callmeDevil on 2019/6/1.
  4. */
  5. public class CashFactory {
  6. /**
  7. * 创建现金收取工厂实例
  8. *
  9. * @param type 收费类型
  10. * @return
  11. */
  12. public static CashSuper createCashAccept(String type) {
  13. CashSuper cs = null;
  14. switch (type) {
  15. case "正常收费":
  16. cs = new CashNormal();
  17. break;
  18. case "满300减100":
  19. cs = new CashReturn(300, 100);
  20. break;
  21. case "打8折":
  22. cs = new CashRebate(0.8);
  23. break;
  24. default:
  25. break;
  26. }
  27. return cs;
  28. }
  29. }
  1. /**
  2. * 现金收费测试
  3. * Created by callmeDevil on 2019/6/1.
  4. */
  5. public class CashTest {
  6. public static void main(String[] args) {
  7. double price = 400;
  8. double num = 3;
  9. System.out.println(String.format("单价:%s 元,数量:%s 个", price, num));
  10. String type = "正常收费";
  11. CashSuper cashSuper = CashFactory.createCashAccept(type);
  12. double total = cashSuper.acceptCash(price) * num;
  13. System.out.println(String.format("折扣:%s;总价:%s 元", type, total));
  14. type = "满300减100";
  15. cashSuper = CashFactory.createCashAccept(type);
  16. total = cashSuper.acceptCash(price) * num;
  17. System.out.println(String.format("折扣:%s;总价:%s 元", type, total));
  18. type = "打8折";
  19. cashSuper = CashFactory.createCashAccept(type);
  20. total = cashSuper.acceptCash(price) * num;
  21. System.out.println(String.format("折扣:%s;总价:%s 元", type, total));
  22. }
  23. }

输出结果

  1. 单价:400.0 元,数量:3.0
  2. 折扣:正常收费;总价:1200.0
  3. 折扣:满300100;总价:900.0
  4. 折扣:打8折;总价:960.0

仍然存在的缺点

简单工厂模式虽然也能够解决问题2,但这个模式只是解决对象的创建问题,而且由于工厂本身包括了所有的收费模式,商场是可能经常性的更改打折额度和返利额度,每次维护或扩展收费方式都要改动这个工厂,以致代码需要重新编译部署,这是很糟糕的,所以不是最好的解决办法。

策略模式

概念

定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化,不回影响到使用算法的客户。

UML图

代码实现

其实上面的简单工厂模式实现方式里面的CashSuper、CashNormal、CashRebate、CashReturn都不需要更改,只需要增加一个CashContext类,同时修改下客户端就可以了。

  1. /**
  2. * 现金上下文
  3. * Created by callmeDevil on 2019/6/1.
  4. */
  5. public class CashContext {
  6. private CashSuper cs = null;
  7. public CashContext(String type) {
  8. switch (type) {
  9. case "正常收费":
  10. cs = new CashNormal();
  11. break;
  12. case "满300减100":
  13. cs = new CashReturn(300, 100);
  14. break;
  15. case "打8折":
  16. cs = new CashRebate(0.8);
  17. break;
  18. default:
  19. break;
  20. }
  21. }
  22. public double getResult(double money) {
  23. return cs.acceptCash(money);
  24. }
  25. }
  1. /**
  2. * 策略模式测试
  3. * Created by callmeDevil on 2019/6/1.
  4. */
  5. public class ContextTest {
  6. public static void main(String[] args) {
  7. double price = 400;
  8. double num = 3;
  9. System.out.println(String.format("单价:%s 元,数量:%s 个", price, num));
  10. String type = "正常收费";
  11. CashContext cashContext = new CashContext(type);
  12. double total = cashContext.getResult(price) * num;
  13. System.out.println(String.format("折扣:%s;总价:%s 元", type, total));
  14. type = "满300减100";
  15. cashContext = new CashContext(type);
  16. total = cashContext.getResult(price) * num;
  17. System.out.println(String.format("折扣:%s;总价:%s 元", type, total));
  18. type = "打8折";
  19. cashContext = new CashContext(type);
  20. total = cashContext.getResult(price) * num;
  21. System.out.println(String.format("折扣:%s;总价:%s 元", type, total));
  22. }
  23. }

需要注意的是

策略模式测试类中的代码与简单工厂的非常相似,因为这里将策略模式与简单工厂模式做了结合,因此比较难以判断策略模式的好处到底在哪。如果仔细分析一下会发现,只用简单工厂的测试类中,也就是客户端代码耦合了CashSuperCashFactory两个类,而使用了策略模式的客户端只涉及到了CashContext一个类,将客户端与具体算法的实现进行了解耦,这样如果商场需要变更促销折扣时,除了变更具体的折扣实现类,只需要更改CashContext即可,客户端完全不用做任何更改,这就是策略模式带来的最大好处。

总结

  • 策略模式是一种定义一系列算法的方法,从概念上看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
  • 策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。
  • 策略模式另一个优点就是简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
  • 当不同的行为堆砌在一个类中,就很难避免使用条件语句来选择合适的行为,将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。
  • 策略模式封装了算法,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
  • 在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象。
  • 最后不得不说的是,每增加一种算法,都免不了修改CashContext中的switch分支,这是没办法的,因为任何需求的变更都需要成本

原文链接:http://www.cnblogs.com/call-me-devil/p/10959205.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号