定义
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
生活中的例子
使用某一种交通方式从家去公司。具体使用哪种交通方式呢?不同的同学会选择不同的交通方式,例如搭乘地铁、自驾、骑行、步行等等,每一种方式都是一种策略;再比如,在购买商品时,可能会使用到不同种类的优惠类型,如满减、折扣、直减、N元购等每一种优惠类型不能共享;
下面以购买商品时使用不同类型优惠方式为例,使用策略模式进行代码实现
- 首先,先看下如果不用设计模式,代码是如何编写的
/*** * 不使用策略模式 */ public class CouponDiscountService { /** * @param type 优惠类型 * @param skuPrice 商品原价 * @param couponPrice 商品优惠券 * @param discountPercent 商品折扣 * @param fullMinusPrice 满减金额 * @return */ public double discountAmount(int type, double skuPrice, double couponPrice, double discountPercent, double fullMinusPrice) { double resultAmount = skuPrice; if (type == 1) { //使用无门槛优惠券 resultAmount = skuPrice - couponPrice; } else if (type == 2) { //折扣 resultAmount = skuPrice * discountPercent; }else if (type == 3) { //满减 resultAmount = skuPrice - fullMinusPrice; }... //若以上方式都不符合,即原价购买 return resultAmount; } }
随着产品功能的增加,优惠类型会不断变化,需要频繁修改和扩展if语句,代码可读性会大大降低。
- 使用策略模式重构代码
首先定义好一个优惠计算行为的接口,参数为泛型,便于动态传参
public interface IcouponDiscount<T> { /** * 优惠金额计算接口 * @param couponInfo 优惠信息 * @param skuPrice 商品原价 * @return 优惠后金额 */ BigDecimal discountAmount(T couponInfo,BigDecimal skuPrice); }
- 满减策略类
public class MJDiscount implements IDiscount<Map<String,String>> { /** * 满减计算,满x元后减n元 * @param couponInfo 优惠信息 * @param skuPrice 商品原价 * @return */ @Override public BigDecimal discountAmount(Map<String, String> couponInfo, BigDecimal skuPrice) { String x = couponInfo.get("x"); String n = couponInfo.get("n"); if (skuPrice.compareTo(new BigDecimal(x)) < 0){ return skuPrice; } return skuPrice.subtract(new BigDecimal(n)); } }
- 直减策略类
public class ZJDiscount implements IDiscount<Double> { /** * 直减计算 * @param couponInfo 无门槛优惠券 * @param skuPrice 商品原价 * @return */ @Override public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) { return skuPrice.subtract(new BigDecimal(couponInfo)); } }
- 折扣策略类
public class ZKDiscount implements IDiscount<Double>{ /** * 折扣计算 * @param couponInfo 折扣比例 * @param skuPrice 商品原价 * @return */ @Override public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) { return skuPrice.multiply(new BigDecimal(couponInfo)).setScale(2,BigDecimal.ROUND_HALF_UP); } }
- N元购策略类
public class NYGDiscount implements IDiscount<Double> { /** * N元购,即无论原价多少,都以固定价格购买(两元,两元,全场两元) * @param couponInfo 优惠信息 * @param skuPrice 商品原价 * @return */ @Override public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) { return new BigDecimal(couponInfo); } }
- 创建枚举类,可根据优惠类型匹配对应的具体实现
public enum DiscountEnum { MJ("1",new MJDiscount()), ZJ("2",new ZJDiscount()), ZK("3",new ZKDiscount()), NYG("4",new NYGDiscount()); public String type; public IDiscount iDiscount; DiscountEnum(String type, IDiscount discount) { this.type = type; this.iDiscount = discount; } /** * 根据优惠类型,匹配对应的枚举值 * @param type * @return 枚举值 */ public static DiscountEnum matchDiscount(String type){ DiscountEnum[] discountEnums = DiscountEnum.values(); for (DiscountEnum discountEnum : discountEnums){ if (type.equals(discountEnum.type)){ return discountEnum; } } return discountEnums[0]; } }
- 测试代码及结果如下
通过使用枚举类,在枚举中将 key 与 规则具体实现进行绑定。以上,策略模式的使用可以减少if else使得代码更加优雅。如果需要新增渠道,我们只需要在编写具体规则实现类并在枚举类中新增的枚举,而不需要改动到原先的任何代码。这符合了开发封闭原则,代码可读性大大增强,更便于维护。
源码分析策略模式的典型应用
Java Comparator 中的策略模式
java.util.Comparator 接口是比较器接口,可以通过 Collections.sort(List,Comparator) 和 Arrays.sort(Object[],Comparator) 对集合和数据进行排序,下面为示例程序
@Data @AllArgsConstructor public class Student { private Integer id; private String name; @Override public String toString() { return "{id=" + id + ", name='" + name + "'}"; } } // 降序 public class DescSortor implements Comparator<Student> { @Override public int compare(Student o1, Student o2) { return o2.getId() - o1.getId(); } } // 升序 public class AscSortor implements Comparator<Student> { @Override public int compare(Student o1, Student o2) { return o1.getId() - o2.getId(); } } //通过 Collections.sort() 对集合List进行排序 public class Test2 { public static void main(String[] args) { List<Student> students = Arrays.asList( new Student(3, "张三"), new Student(1, "李四"), new Student(4, "王五"), new Student(2, "赵六") ); toString(students, "排序前"); Collections.sort(students, new AscSortor()); toString(students, "升序后"); Collections.sort(students, new DescSortor()); toString(students, "降序后"); } public static void toString(List<Student> students, String desc) { for (Student student : students) { System.out.print(desc + ": " + student.toString() + ", "); } System.out.println(); } }
排序前:
{id=3, name='张三'},
{id=1, name='李四'},
{id=4, name='王五'},
{id=2, name='赵六'}
升序后:
{id=1, name='李四'},
{id=2, name='赵六'},
{id=3, name='张三'},
{id=4, name='王五'}
降序后:
{id=4, name='王五'},
{id=3, name='张三'},
{id=2, name='赵六'},
{id=1, name='李四'}
这里 Comparator 接口充当了抽象策略角色,两个比较器 DescSortor 和 AscSortor 则充当了具体策略角色。
到此,聪明的你学会了吗?