Java的策略模式

今天难得母亲大人休息,跟外婆妹妹她们一起去喝早茶,本来是很平常的一餐,但当结账时我看到了餐厅上写的打折优惠(具体部分没有拍照,大致是下面这样子),心里若有所思,觉得跟 Java 的策略模式很相像。刚好我也没有具体去实现过策略模式,趁着这个机会,自己试着实现了一下策略模式。

08:30 - 11:30 打 6.8 折	以结算时间为准
12:00 - 17:00 打 8.8 折	以入单时间为准
19:30 - 22:00 打 7.0 折	以入单时间为准

需求(自己添加的)
根据入单时间和结算时间,计算本次的消费金额

当我写完后,去网上看了一下具体的策略模式,发现网上的好像比我写的要简单一点,网上的是根据具体的策略手动 new 出来一个策略对象的🤣。下面贴一下我的代码,如果这不是策略模式的话,请轻喷!还请各位不吝赐教!!!

先贴一个最朴素的做法

public class StrategyMain {
    static Calendar startTime = Calendar.getInstance();
    static Calendar endTime = Calendar.getInstance();

    public static void main(String[] args) {

        startTime.set(2022, Calendar.OCTOBER, 24, 9, 0);
        endTime.set(2022, Calendar.OCTOBER, 24, 10, 32);

        Order order = new Order(126, new Date(startTime.getTimeInMillis()), new Date(endTime.getTimeInMillis()));

        if (inGo(order.getEndTime().getTime())) {
            System.out.println("消费金额为 " + order.getPay() * 0.68 + " 元,打 6.8 折");
        } else if (inAm(order.getStartTime().getTime())) {
            System.out.println("消费金额为 " + order.getPay() * 0.88 + " 元, 打 8.8 折");
        } else if (inPm(order.getStartTime().getTime())) {
            System.out.println("消费金额为 " + order.getPay() * 0.70 + " 元, 打 7.0 折");
        } else {
            System.out.println("消费金额为 " + order.getPay() + " 元,没有打折");
        }


    }

    // 08:30 - 11:30 打 6.8 折	以结算时间为准
    private static boolean inGo(long t) {
        startTime.set(2022, Calendar.OCTOBER, 24, 8, 30);
        endTime.set(2022, Calendar.OCTOBER, 24, 11, 30);

        return startTime.getTimeInMillis() <= t && t <= endTime.getTimeInMillis();
    }

    // 12:00 - 17:00 打 8.8 折	以入单时间为准
    private static boolean inAm(long t) {
        startTime.set(2022, Calendar.OCTOBER, 24, 12, 0);
        endTime.set(2022, Calendar.OCTOBER, 24, 17, 0);

        return startTime.getTimeInMillis() <= t && t <= endTime.getTimeInMillis();
    }

    // 19:30 - 22:00 打 7.0 折	以入单时间为准
    private static boolean inPm(long t) {
        startTime.set(2022, Calendar.OCTOBER, 24, 19, 30);
        endTime.set(2022, Calendar.OCTOBER, 24, 22, 0);

        return startTime.getTimeInMillis() <= t && t <= endTime.getTimeInMillis();
    }

输出结果

消费金额为 85.68 元,打 6.8 折

下面是使用了策略模式的做法

先定义订单实体类 Order (简化)

package com.zmz.entity;

import java.util.Date;

public class Order {

    private double pay; // 支付金额
    private Date startTime; // 开始时间
    private Date endTime; // 结束时间

    public Order() {
    }

    public Order(double pay, Date startTime, Date endTime) {
        this.pay = pay;
        this.startTime = startTime;
        this.endTime = endTime;
    }

    public double getPay() {
        return pay;
    }

    public void setPay(double pay) {
        this.pay = pay;
    }

    public Date getStartTime() {
        return startTime;
    }

    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }

    public Date getEndTime() {
        return endTime;
    }

    public void setEndTime(Date endTime) {
        this.endTime = endTime;
    }

    @Override
    public String toString() {
        return "Order{" +
                "pay=" + pay +
                ", startTime=" + startTime +
                ", endTime=" + endTime +
                '}';
    }
}

策略接口

package com.zmz.strategy;

import com.zmz.entity.Order;

import java.util.Calendar;

public interface OrderStrategy {

    /**
     * 根据传入的开始时间和结束时间,选择具体的策略
     */
    default void strategy(Order order) {
        System.out.println("消费金额为 " + order.getPay() + " 元,没有打折");
    }

}

具体策略

// 08:30 - 11:30 打 6.8 折	以结算时间为准
package com.zmz.strategy.impl;

import com.zmz.entity.Order;
import com.zmz.strategy.StrategyEnum;
import com.zmz.strategy.OrderStrategy;

public class OrderStrategyGo implements OrderStrategy {

    @Override
    public void strategy(Order order) {
        System.out.println("消费金额为 " + order.getPay() * StrategyEnum.GO.getDiscount() + " 元,打 6.8 折");
    }

}
// 12:00 - 17:00 打 8.8 折	以入单时间为准
package com.zmz.strategy.impl;

import com.zmz.entity.Order;
import com.zmz.strategy.StrategyEnum;
import com.zmz.strategy.OrderStrategy;

public class OrderStrategyAm implements OrderStrategy {

    @Override
    public void strategy(Order order) {
        System.out.println("消费金额为 " + order.getPay() * StrategyEnum.AM.getDiscount() + " 元, 打 8.8 折");
    }

}
// 19:30 - 22:00 打 7.0 折	以入单时间为准
package com.zmz.strategy.impl;

import com.zmz.entity.Order;
import com.zmz.strategy.StrategyEnum;
import com.zmz.strategy.OrderStrategy;

public class OrderStrategyPm implements OrderStrategy {

    @Override
    public void strategy(Order order) {
        System.out.println("消费金额为 " + order.getPay() * StrategyEnum.PM.getDiscount() + " 元, 打 7.0 折");
    }

}

为了方便,写了一个枚举类

package com.zmz.strategy;

import com.zmz.strategy.impl.OrderStrategyAm;
import com.zmz.strategy.impl.OrderStrategyGo;
import com.zmz.strategy.impl.OrderStrategyPm;

public enum StrategyEnum {
    GO, // 08:30 - 11:30 打 6.8 折	以结算时间为准
    AM, // 12:00 - 17:00 打 8.8 折	以入单时间为准
    PM, // 19:30 - 22:00 打 7.0 折	以入单时间为准
    DEFAULT;

    private int low; // 满足该策略的时间最小分数
    private int height; // 满足该策略的时间最大分数
    private double discount; // 具体折扣
    private OrderStrategy orderStrategy; // 持有的策略

    static {
        init(StrategyEnum::GOInit);
        init(StrategyEnum::AMInit);
        init(StrategyEnum::PMInit);
        init(StrategyEnum::defaultInit);
    }

    StrategyEnum() {
    }

    private static void init(Runnable runnable) {
        runnable.run();
    }

    private static void GOInit() {
        GO.low = 8 * 60 + 30;
        GO.height = 11 * 60 + 30;
        GO.discount = 0.68;
        GO.orderStrategy = new OrderStrategyGo();
    }

    private static void AMInit() {
        AM.low = 12 * 60 + 0;
        AM.height = 17 * 60 + 0;
        AM.orderStrategy = new OrderStrategyAm();
        AM.discount = 0.88;
    }

    private static void PMInit() {
        PM.low = 19 * 60 + 30;
        PM.height = 22 * 60 + 0;
        PM.discount = 0.70;
        PM.orderStrategy = new OrderStrategyPm();
    }

    private static void defaultInit() {
        DEFAULT.discount = 1.0;
        DEFAULT.orderStrategy = new OrderStrategy() {
        };
    }

    public int getLow() {
        return low;
    }

    public int getHeight() {
        return height;
    }

    public double getDiscount() {
        return discount;
    }

    public OrderStrategy getStrategyService() {
        return orderStrategy;
    }
}

补充

枚举类的 low 和 height 属性说明

各个策略的选择只与订单的入单时间或结单时间的小时和分钟有关系,所以可以将具体的时间转换为一个唯一的值,简化选择策略的步骤。具体算法: 小时数 * 60 + 分钟数

策略上下文

package com.zmz.strategy;

import java.util.Date;

public class StrategyContext {


    public static OrderStrategy chooseStrategy(long start, long end) {
        switch (chooseEnum(start, end)) {
            case GO:
                return StrategyEnum.GO.getStrategyService();
            case AM:
                return StrategyEnum.AM.getStrategyService();
            case PM:
                return StrategyEnum.PM.getStrategyService();
            default:
                return StrategyEnum.DEFAULT.getStrategyService();

        }
    }

    private static StrategyEnum chooseEnum(long start, long end) {
        int low = new Date(start).getHours() * 60 + new Date(start).getMinutes();
        int height = new Date(end).getHours() * 60 + new Date(end).getMinutes();

        if (StrategyEnum.GO.getLow() < height && height <= StrategyEnum.GO.getHeight()) {
            return StrategyEnum.GO;
        } else if (StrategyEnum.AM.getLow() <= low && low < StrategyEnum.AM.getHeight()) {
            return StrategyEnum.AM;
        } else if (StrategyEnum.PM.getLow() <= low && low < StrategyEnum.PM.getHeight()) {
            return StrategyEnum.PM;
        }

        return StrategyEnum.DEFAULT;
    }


}

测试

package com.zmz;

import com.zmz.entity.Order;
import com.zmz.strategy.OrderStrategy;
import com.zmz.strategy.StrategyContext;

import java.util.Calendar;
import java.util.Date;

public class StrategyMain {
    static Calendar startTime = Calendar.getInstance();
    static Calendar endTime = Calendar.getInstance();

    public static void main(String[] args) {

        startTime.set(2022, Calendar.OCTOBER, 24, 9, 0);
        endTime.set(2022, Calendar.OCTOBER, 24, 10, 32);
        Order order = new Order(126, new Date(startTime.getTimeInMillis()), new Date(endTime.getTimeInMillis()));

        OrderStrategy orderStrategy = StrategyContext.chooseStrategy(order.getStartTime().getTime(), order.getEndTime().getTime());
        orderStrategy.strategy(order);


    }
}

输出

消费金额为 85.68 元,打 6.8 折

最后

只要知道订单的入单时间和结单时间,就能自动选择策略来确定具体的折扣!