一文读懂策略模式

简介: 本文通过举例生活中常见的购买商品享受不同优惠方式的例子来详细阐述策略模式的使用方式,以及通过分析Java源码中 Comparator 类的策略模式使用示例来详细阐述了策略模式的使用场景和作用。

定义

定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。


在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。


生活中的例子

使用某一种交通方式从家去公司。具体使用哪种交通方式呢?不同的同学会选择不同的交通方式,例如搭乘地铁、自驾、骑行、步行等等,每一种方式都是一种策略;再比如,在购买商品时,可能会使用到不同种类的优惠类型,如满减、折扣、直减、N元购等每一种优惠类型不能共享;


下面以购买商品时使用不同类型优惠方式为例,使用策略模式进行代码实现

  1. 首先,先看下如果不用设计模式,代码是如何编写的
/***
 * 不使用策略模式
 */
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语句,代码可读性会大大降低。


  1. 使用策略模式重构代码

首先定义好一个优惠计算行为的接口,参数为泛型,便于动态传参

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);
    }
}


  1. 创建枚举类,可根据优惠类型匹配对应的具体实现
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];
    }
}
  1. 测试代码及结果如下

通过使用枚举类,在枚举中将 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 则充当了具体策略角色。

到此,聪明的你学会了吗?

相关文章
|
6月前
|
设计模式 算法
二十三种设计模式全面解析-深入解析模板方法模式的奇妙世界
二十三种设计模式全面解析-深入解析模板方法模式的奇妙世界
|
6月前
|
设计模式 算法 Java
小谈设计模式(3)—策略模式
小谈设计模式(3)—策略模式
|
设计模式 Oracle Java
【设计模式】我终于读懂了桥接模式。。。
【设计模式】我终于读懂了桥接模式。。。
【设计模式】我终于读懂了桥接模式。。。
|
6月前
|
设计模式 算法 Java
二十三种设计模式全面解析-当你的代码需要多种算法时,策略模式是你的救星!
二十三种设计模式全面解析-当你的代码需要多种算法时,策略模式是你的救星!
|
设计模式 JavaScript 前端开发
超级简单的设计模式,看不懂你来打我
超级简单的设计模式,看不懂你来打我
|
设计模式 Java 关系型数据库
我终于读懂了工厂模式。。。
我终于读懂了工厂模式。。。
我终于读懂了工厂模式。。。
|
设计模式 关系型数据库
【设计模式】我终于读懂了装饰者模式。。。(上)
【设计模式】我终于读懂了装饰者模式。。。
【设计模式】我终于读懂了装饰者模式。。。(上)
|
设计模式 Java
【设计模式】我终于读懂了装饰者模式。。。(下)
【设计模式】我终于读懂了装饰者模式。。。(下)
【设计模式】我终于读懂了装饰者模式。。。(下)
我终于读懂了适配器模式。。。
我终于读懂了适配器模式。。。
我终于读懂了适配器模式。。。
|
设计模式 Java
【设计模式】我终于读懂了组合模式。。。(下)
【设计模式】我终于读懂了组合模式。。。(下)
【设计模式】我终于读懂了组合模式。。。(下)