谈起这个模式,就想起了老四当年面试的时候就被问到了这个,那时候大菜逼一个(现在更特么菜),面试官当时是这么问我的:商家搞活动,对于商品最后的计算来说可能有不同的计算方式,学生可能半价,普通会员可能就是活动8折,vip可能满100减50,有的人原价购买返还积分,这种情况下最好的设计模式用哪个?
老四当时他妈一脸懵逼啊,大学不好好学习,知道的设计模式无非是什么单例,简单工厂这类的,对于这种问题一下子就给我问懵逼了,灰头土脸的就回来了,今天终于有机会写写这个问题了,并且我保证我以后在特么也不会在这个”策略模式”上面掉链子了,同时也希望你看完这篇文章之后也能理解这个牛逼的设计模式,不仅仅是为了面试,是为了更好的学以致用。
少啰嗦,开始装逼~~~~~
定义
策略模式(Strategy):定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户.从概念上来看,所有这些算法完成的都是相同的工作,但是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
看这种书面式的定义永远都是丈二的和尚~~,用点东北大白话来讲吧,其实也是策略模式的目的,策略模式其实就是将当前业务场景可能用到的算法一个个的都封装成类,然后对于使用这些算法的环境来讲,让他们跟算法的抽象类来交互,从而符合依赖倒转原则,关于依赖倒转原则可以参考老四的这篇文章《浅析设计模式第五章之依赖倒转原则》。这样再出现新的算法时,只需要增加一个新的实现抽象策略类的实现类就好了,这些算法还能复用到其他的业务场景上面,两全其美。而且策略模式的优点可以看出来就是简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。说到这里可能我们的思路比较清晰了,接下来就用代码一点点感受一下吧。
最好的例子估计就是这种打折、满减的例子,这里以电影票为例,学生证半价,网购8.8折,儿童立减30元,vip5折之外还返还卡的积分等等。在不用策略模式之前,我们最简单写法可能就是超屌的if-else if-else句式了。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
package com.glorze.general; import java.math.BigDecimal; /** * 电影票 普通的计算票价的方式 学生证半价,网购8.8折,儿童立减30元,vip5折之外还返还卡的积分 * @ClassName CinemaTicket * @author: glorze.com * @since: 2018年6月5日 下午11:32:57 */ public class CinemaTicket { /** * 票价,原价,定价 */ private BigDecimal price; /** * 电影票类型: 学生票 会员票 网购票 儿童票 */ private String ticketType; public BigDecimal getPrice() { return this.computePrice(); } public void setPrice(BigDecimal price) { this.price = price; } public String getTicketType() { return ticketType; } public void setTicketType(String ticketType) { this.ticketType = ticketType; } @Override public String toString() { return "CinemaTicket [price=" + price + ", ticketType=" + ticketType + "]"; } /** * 计算不通过类型的电影票最终价格 * @Title: computePrice * @return BigDecimal * @author: 高老四博客 * @since: 2018年6月5日 下午11:38:58 */ public BigDecimal computePrice() { // 魔法值定义 String student = "student"; String children = "children"; String vip = "vip"; String online = "online"; // 学生票半价 if (this.ticketType.equalsIgnoreCase(student)) { System.out.println("学生票: "); return this.price.multiply(new BigDecimal("0.5")); } else if (this.ticketType.equalsIgnoreCase(children)) { // 儿童票1折 System.out.println("儿童票: "); return this.price.subtract(new BigDecimal("30")); } else if (this.ticketType.equalsIgnoreCase(vip)) { // VIP票折后票价计算 System.out.println("VIP会员-返还积分"); return this.price.multiply(new BigDecimal("0.5")); } else if (this.ticketType.equalsIgnoreCase(online)) { // VIP票折后票价计算 System.out.println("网购票: "); return this.price.multiply(new BigDecimal("0.88")); } else { // 如果不满足任何打折要求,则返回原始票价 return this.price; } } public static void main(String[] args) { CinemaTicket cinemaTicket = new CinemaTicket(); BigDecimal originalPrice = new BigDecimal("88"); BigDecimal finalPrice; cinemaTicket.setPrice(originalPrice); System.out.println("电影票原价(定价)为: " + originalPrice); System.out.println("---------------------------------"); cinemaTicket.setTicketType("student"); finalPrice = cinemaTicket.getPrice(); System.out.println("学生票折后价为: " + finalPrice); System.out.println("---------------------------------"); cinemaTicket.setTicketType("children"); finalPrice = cinemaTicket.getPrice(); System.out.println("学生票折后价为: " + finalPrice); System.out.println("---------------------------------"); cinemaTicket.setTicketType("vip"); finalPrice = cinemaTicket.getPrice(); System.out.println("学生票折后价为: " + finalPrice); System.out.println("---------------------------------"); cinemaTicket.setTicketType("online"); finalPrice = cinemaTicket.getPrice(); System.out.println("学生票折后价为: " + finalPrice); System.out.println("---------------------------------"); } } |
以上的代码很清晰的能发现很多问题: 首先,大量的if-else语句会导致computePrice()方法越来越臃肿,后期不利于维护。违反”开放-封闭原则”,如果需要修改打折的折数需要修改这个类,关于”开放-封闭原则”可以参考这篇文章《浅析设计模式第四章之开放-封闭原则》。其次,代码的复用性以及可移植性差,算法与当前的业务场景耦合性太大。这个时候就需要策略模式出手了~~
前面说到,策略模式的目的是将业务场景与算法分开,每个策略都会封装某一种算法,把算法的责任和算法本身分割开,委派给不同的对象管理。比如说打折算法,满减算法,积分算法等,他们都是一个个的策略类,然后针对业务场景我们维护业务场景类,也叫环境类,即 使用了这些算法的场景类,这样也符合了”依赖倒转原则”。总结一下策略模式中涉及到的几个具象的类:
- Context(环境类): 环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。就是我们所说的业务场景类。
- Strategy(抽象策略类): 它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。就是定义算法的接口。
- ConcreteStrategy(具体策略类): 它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。就是具体的算法。
还是具体的代码生动一些,我们将上述代码改写成策略模式的样子来体会一下。环境类依然叫做CinemaTicket,然后我们还需要定义抽象策略(电影票算法)类来为各种不同的算法提供具体实现的继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
package com.glorze.strategy; import java.math.BigDecimal; /** * 电影票 普通的计算票价的方式 学生证半价,网购8.8折,儿童立减30元,vip5折之外还返还卡的积分 * @ClassName CinemaTicket * @author: glorze.com * @since: 2018年6月11日 下午11:04:40 */ public class CinemaTicket { /** * 票价,原价,定价 */ private BigDecimal price; /** * 调用抽象折扣类 */ private TicketDiscount ticketDiscount; public BigDecimal getPrice() { return ticketDiscount.computePrice(price); } public void setPrice(BigDecimal price) { this.price = price; } public TicketDiscount getTicketDiscount() { return ticketDiscount; } public void setTicketDiscount(TicketDiscount ticketDiscount) { this.ticketDiscount = ticketDiscount; } public static void main(String[] args) { CinemaTicket cinemaTicket = new CinemaTicket(); BigDecimal originalPrice = new BigDecimal("88"); cinemaTicket.setPrice(originalPrice); // 如果这里为了进一步提高系统的灵活性和扩展性,可以已是用Java反射创建对应的实例 // 例如是用XML文件配置想要创建的实例,这样无需修改源代码,只需配置配置文件即可 TicketDiscount td = new StudentTicketDiscount(); cinemaTicket.setTicketDiscount(td); System.out.println(cinemaTicket.getPrice()); } } |
影票优惠接口类,改接口定义计算票价的方法,即上述的抽象策略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.glorze.strategy; import java.math.BigDecimal; /** * 影票优惠计算抽象策略接口 * @ClassName TicketDiscount * @author: glorze.com * @since: 2018年6月16日 下午9:50:27 */ public interface TicketDiscount { /** * 计算电影票的价格 * @Title: computePrice * @param price * @return BigDecimal * @author: 高老四博客 * @since: 2018年6月16日 下午9:50:51 */ public BigDecimal computePrice(BigDecimal price); } |
各个具体的角色所能拿到对应优惠票价的实现类,这里只放出一个,全部项目代码可以文末自助获取下载参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package com.glorze.strategy; import java.math.BigDecimal; /** * 儿童票价计算具体策略类 * @ClassName ChildrenTicketDiscount * @author: glorze.com * @since: 2018年6月16日 下午9:52:27 */ public class ChildrenTicketDiscount implements TicketDiscount{ @Override public BigDecimal computePrice(BigDecimal price) { System.out.println("儿童票: "); return price.subtract(new BigDecimal("30")); } } |
怎么样,系统是不是一下就灵活了起来,策略模式的自由切换以及灵活的扩展性使其成为了非常常见的设计模式,如果你在业务场景也遇到了类似的很多不同算法取其一的这种情况可以考虑使用一下。接下来我们再精炼的总结一下策略模式。
策略模式的优点总结:
- 算法重用: 策略模式提供了一种算法的复用机制,Strategy类层次为Context定义了一系列的可供重用的算法或行为.由于将算法单独提取出来封装在策略类中,因此不同的环境类可以方便地复用这些策略类。
- 消除条件判断语句: 多重条件选择语句不易维护,当不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为.所以将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。
- 单元测试更独立更简单
- 完美符合”开闭原则”,可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
策略模式的缺点总结:
- 策略模式只适用于客户端知道所有的算法或行为的情况。因为客户端必须知道所有的策略类,并自行决定使用哪一个策略类。所以就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。
- 会导致具体策略类累积的越来越多。细小的变化都需要新增一个策略类来实现。
- 客户端只能使用一个策略,无法同时多个。
建议使用策略模式的场景: 策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性:
- 某系统需要在多种算法中选择一种。
- 某个对象有很多行为,实例选择一种行为来实现具体的功能就可以考虑策略模式,注意避免使用多重的条件选择语句来造成系统的耦合性过高。
使用策略模式的几个例子,诸位可以前去了解一下:
- Spring初始化Bean的实例
- Spring事务的实现
- J2SE(java2 Standard edition,java2标准版)的容器布局管理
- 微软的PetShop项目
完整项目下载地址文末自助获取。注册之后即可下载,不要钱的,一个注册只是提高一下学习的门槛,老四没那么势力!!
更博不易,喜欢的老铁有能力烦请赞助盒烟钱,点我去赞助。抱拳。
资源下载
隐藏内容:******,购买后可见!
下载价格:0 G币
您需要先登录后,才能购买资源
欢迎访问高老四博客(glorze.com),本站技术文章代码均为老四亲自编写或者借鉴整合,其余资源多为网络收集,如涉及版权问题请与站长联系。如非特殊说明,本站所有资源解压密码均为:glorze.com。