大话设计模式笔记(二)の策略模式
举个栗子
问题描述
商场收银软件,营业员根据客户所购买的商品单价和数量,向客户收费。
简单实现
1. /**
2. * 普通实现
3. * Created by callmeDevil on 2019/6/1.
4. */
5. public class NormalTest {
6.
7. public static void main(String[] args) {
8. double price = 10;
9. double num = 5;
10. System.out.println(String.format("单价:%s 元", price));
11. System.out.println(String.format("数量:%s 个", num));
12. System.out.println(String.format("总价:%s 元", calculateTotal(price, num)));
13. }
14.
15. /**
16. * 计算总价
17. *
18. * @param price 单价
19. * @param num 数量
20. * @return
21. */
22. private static double calculateTotal(double price, double num) {
23. return price * num;
24. }
25.
26. }
27.
问题2
商品搞促销,打八折,也可能打七折,甚至五折。
数组实现
1. /**
2. * 普通实现2
3. * Created by callmeDevil on 2019/6/1.
4. */
5. public class NormalTest2 {
6.
7. public static void main(String[] args) {
8. double price = 10;
9. double num = 5;
10. String[] discounts = {"正常收费", "打八折", "打七折", "打五折"};
11. System.out.println(String.format("单价:%s 元", price));
12. System.out.println(String.format("数量:%s 个", num));
13. System.out.println(String.format("折扣:%s ", discounts[1]));
14. System.out.println(String.format("总价:%s 元", calculateTotal(price, num, 1)));
15. }
16.
17. /**
18. * 计算总价
19. *
20. * @param price 单价
21. * @param num 数量
22. * @param discount 折扣
23. * @return
24. */
25. private static double calculateTotal(double price, double num, int discount) {
26. double total = 0L;
27. switch (discount) {
28. case 0:
29. total = price * num;
30. break;
31. case 1:
32. total = price * num * 0.8;
33. break;
34. case 2:
35. total = price * num * 0.7;
36. break;
37. case 3:
38. total = price * num * 0.5;
39. break;
40. default:
41. break;
42. }
43. return total;
44. }
45.
46. }
47.
上述方式存在问题
有很多重复代码,就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. }
14.
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. }
12.
13.
1. /**
2. * 返利收费子类
3. * Created by callmeDevil on 2019/6/1.
4. */
5. public class CashReturn extends CashSuper{
6.
7. // 返利条件
8. private double moneyCondition = 0;
9. // 返利值
10. private double moneyReturn = 0;
11.
12. // 返利收费,初始化时必须输入返利条件和返利值,比如满300返100,
13. // 则moneyCondition 为300,moneyReturn 为100
14. public CashReturn(double moneyCondition, double moneyReturn) {
15. this.moneyCondition = moneyCondition;
16. this.moneyReturn = moneyReturn;
17. }
18.
19. @Override
20. public double acceptCash(double money) {
21. double result = money;
22. if (money >= moneyCondition) {
23. // 若大于返利条件,则需要减去返利值
24. result = money - Math.floor(money / moneyCondition) * moneyReturn;
25. }
26. return result;
27. }
28.
29. }
30.
1. /**
2. * 打折收费子类
3. * Created by callmeDevil on 2019/6/1.
4. */
5. public class CashRebate extends CashSuper{
6.
7. // 折扣率
8. private double moneyRebate = 1;
9.
10. public CashRebate(double moneyRebate) {
11. // 打折收费,初始化时,必须输入折扣率,如打八折,就是0.8
12. this.moneyRebate = moneyRebate;
13. }
14.
15. @Override
16. public double acceptCash(double money) {
17. return money * moneyRebate;
18. }
19.
20. }
21.
1. /**
2. * 现金收费工厂类
3. * Created by callmeDevil on 2019/6/1.
4. */
5. public class CashFactory {
6.
7. /**
8. * 创建现金收取工厂实例
9. *
10. * @param type 收费类型
11. * @return
12. */
13. public static CashSuper createCashAccept(String type) {
14. CashSuper cs = null;
15. switch (type) {
16. case "正常收费":
17. cs = new CashNormal();
18. break;
19. case "满300减100":
20. cs = new CashReturn(300, 100);
21. break;
22. case "打8折":
23. cs = new CashRebate(0.8);
24. break;
25. default:
26. break;
27. }
28. return cs;
29. }
30.
31. }
32.
1. /**
2. * 现金收费测试
3. * Created by callmeDevil on 2019/6/1.
4. */
5. public class CashTest {
6.
7. public static void main(String[] args) {
8. double price = 400;
9. double num = 3;
10. System.out.println(String.format("单价:%s 元,数量:%s 个", price, num));
11.
12. String type = "正常收费";
13. CashSuper cashSuper = CashFactory.createCashAccept(type);
14. double total = cashSuper.acceptCash(price) * num;
15. System.out.println(String.format("折扣:%s;总价:%s 元", type, total));
16.
17. type = "满300减100";
18. cashSuper = CashFactory.createCashAccept(type);
19. total = cashSuper.acceptCash(price) * num;
20. System.out.println(String.format("折扣:%s;总价:%s 元", type, total));
21.
22. type = "打8折";
23. cashSuper = CashFactory.createCashAccept(type);
24. total = cashSuper.acceptCash(price) * num;
25. System.out.println(String.format("折扣:%s;总价:%s 元", type, total));
26. }
27.
28. }
29.
输出结果
1. 单价:400.0 元,数量:3.0 个
2. 折扣:正常收费;总价:1200.0 元
3. 折扣:满300减100;总价:900.0 元
4. 折扣:打8折;总价:960.0 元
5.
仍然存在的缺点
简单工厂模式虽然也能够解决问题2,但这个模式只是解决对象的创建问题,而且由于工厂本身包括了所有的收费模式,商场是可能经常性的更改打折额度和返利额度,每次维护或扩展收费方式都要改动这个工厂,以致代码需要重新编译部署,这是很糟糕的,所以不是最好的解决办法。
策略模式
概念
定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化,不回影响到使用算法的客户。
UML图
代码实现
其实上面的简单工厂模式实现方式里面的CashSuper、CashNormal、CashRebate、CashReturn都不需要更改,只需要增加一个CashContext类,同时修改下客户端就可以了。
1. /**
2. * 现金上下文
3. * Created by callmeDevil on 2019/6/1.
4. */
5. public class CashContext {
6.
7. private CashSuper cs = null;
8.
9. public CashContext(String type) {
10. switch (type) {
11. case "正常收费":
12. cs = new CashNormal();
13. break;
14. case "满300减100":
15. cs = new CashReturn(300, 100);
16. break;
17. case "打8折":
18. cs = new CashRebate(0.8);
19. break;
20. default:
21. break;
22. }
23. }
24.
25. public double getResult(double money) {
26. return cs.acceptCash(money);
27. }
28.
29. }
30.
1. /**
2. * 策略模式测试
3. * Created by callmeDevil on 2019/6/1.
4. */
5. public class ContextTest {
6.
7. public static void main(String[] args) {
8. double price = 400;
9. double num = 3;
10. System.out.println(String.format("单价:%s 元,数量:%s 个", price, num));
11.
12. String type = "正常收费";
13. CashContext cashContext = new CashContext(type);
14. double total = cashContext.getResult(price) * num;
15. System.out.println(String.format("折扣:%s;总价:%s 元", type, total));
16.
17. type = "满300减100";
18. cashContext = new CashContext(type);
19. total = cashContext.getResult(price) * num;
20. System.out.println(String.format("折扣:%s;总价:%s 元", type, total));
21.
22. type = "打8折";
23. cashContext = new CashContext(type);
24. total = cashContext.getResult(price) * num;
25. System.out.println(String.format("折扣:%s;总价:%s 元", type, total));
26. }
27.
28. }
29.
需要注意的是
策略模式测试类中的代码与简单工厂的非常相似,因为这里将策略模式与简单工厂模式做了结合,因此比较难以判断策略模式的好处到底在哪。如果仔细分析一下会发现,只用简单工厂的测试类中,也就是客户端代码耦合了CashSuper和CashFactory两个类,而使用了策略模式的客户端只涉及到了CashContext一个类,将客户端与具体算法的实现进行了解耦,这样如果商场需要变更促销折扣时,除了变更具体的折扣实现类,只需要更改CashContext即可,客户端完全不用做任何更改,这就是策略模式带来的最大好处。
总结
- 策略模式是一种定义一系列算法的方法,从概念上看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
- 策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。
- 策略模式另一个优点就是简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
- 当不同的行为堆砌在一个类中,就很难避免使用条件语句来选择合适的行为,将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。
- 策略模式封装了算法,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
- 在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象。
- 最后不得不说的是,每增加一种算法,都免不了修改CashContext中的switch分支,这是没办法的,因为任何需求的变更都需要成本。
Pass:以上纯属个人理解~~如果发现有错或是心存建议意见等,欢迎大家评论或联系~(# ゚Д゚)~祝大家身体健康学习进步工作顺利生活愉快!
版权归 callmeDevil 所有,如需转载请标注转载来源
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(三):用.NET IoT库
· 【非技术】说说2024年我都干了些啥