Java策略模式|规则对象化

前言

  1. Java策略模式是经常使用的一个设计模式,其通过规则驱动,将不同的规则以及不同的实现逻辑,通过封装成类,实现消除复杂的平行规则。
  2. 而传统的策略模式属于类级别抽象,一个规则会抽象成类级别,策略模式虽然不复杂,但是很笨重。
  3. 而轻量级的策略模式,则把类级别抽象换为函数级别抽象,将相似的规则、相似的逻辑一同封装进一个函数接口中,通过Lambda表达式进行不同规则的编写。
  4. 这种函数级策略模式,是即时创建、即时使用、即时删除的,避免了传统策略模式导致的类爆炸,同时又沿用了策略模式的高可拓展、高可维护性。
  5. 函数级策略模式在特定场景下,对传统策略模式有着碾压级别的优势,但是并不意味着传统策略模式会被替代,在特定的情况下,其依旧是消除代码大规模显性规则的利器。(例如if膨胀问题)

Let's Start!

1. 传统场景模式

1. 场景描述

  1. 现需要一个方法,不同的手机品牌会有不同的折扣,而该折扣是分阶段实行的,给出以下几种折扣方式

品牌

价格区间

折扣率

附加优惠

小米

≤ 1000元

9.8折

1000元 < 价格 ≤ 2000元

9.5折

赠有线耳机

价格 > 2000元

9.2折

赠小米手环

品牌

价格区间

折扣率

附加优惠

华为

≤ 1500元

9.5折

1500元 < 价格 ≤ 3000元

9折

赠FreeBuds SE

价格 > 3000元

8.5折

24期免息

品牌

价格区间

折扣率

附加优惠

iPhone

≤ 5000元

无折扣

赠AirTag

5000元 < 价格 ≤ 8000元

9.8折

12期免息

价格 > 8000元

9.5折

24期免息 + 赠MagSafe

2. 代码实现

  • 核心方法实现
/**
 * 手机服务类
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
@Slf4j
@Service
public class MobileService {

    /**
     * 购买手机
     *
     * @param mobileBrand 手机品牌
     * @param price       手机价格
     * @return 购买结果
     */
    public String buyMobile(String mobileBrand, Integer price) {
        StringBuilder buyResult = new StringBuilder();

        if (price == null) {
            buyResult.append("手机价格不能为空");
            return buyResult.toString();
        }

        if (MobileBrand.XIAOMI.getBrand().equals(mobileBrand)) {
            // 小米品牌,根据价格进行购买逻辑
            if (price <= 1000) {
                buyResult.append("购买小米手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.98D);
            } else if (price <= 2000) {
                buyResult.append("购买小米手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.95D).append(" 附赠有线耳机");
            } else {
                buyResult.append("购买小米手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.9D).append(" 附赠小米手环");
            }
        } else if (MobileBrand.HUAWEI.getBrand().equals(mobileBrand)) {
            // 华为品牌,根据价格进行购买逻辑
            if (price <= 1500) {
                buyResult.append("购买华为手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.95D);
            } else if (price <= 3000) {
                buyResult.append("购买华为手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.9D).append(" 附赠FreeBuds SE");
            } else {
                buyResult.append("购买华为手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.85D).append(" 24期免息");
            }
        } else if (MobileBrand.APPLE.getBrand().equals(mobileBrand)) {
            // 苹果品牌,根据价格进行购买逻辑
            if (price <= 5000) {
                buyResult.append("购买苹果手机成功,").append("手机价格: ").append(price).append(" 附赠AirTag");
            } else if (price <= 8000) {
                buyResult.append("购买苹果手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.98D).append(" 12期免息");
            } else {
                buyResult.append("购买苹果手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.95D).append(" 24期免息 附赠MagSafe");
            }
        } else {
            buyResult.append("手机品牌不存在");
        }

        return buyResult.toString();
    }
}

可以看出,代码中的if有了嵌套层级,且略显臃肿,我们进行逻辑提取,进行初步优化

  • 初步优化版本
/**
 * 手机服务类
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
@Slf4j
@Service
public class MobileService {

    /**
     * 购买手机
     *
     * @param mobileBrand 手机品牌
     * @param price       手机价格
     * @return 购买结果
     */
    public String buyMobile(String mobileBrand, Integer price) {
        StringBuilder buyResult = new StringBuilder();

        if (price == null) {
            buyResult.append("手机价格不能为空");
            return buyResult.toString();
        }

        if (MobileBrand.XIAOMI.getBrand().equals(mobileBrand)) {
            buyXiaoMi(price, buyResult);
        } else if (MobileBrand.HUAWEI.getBrand().equals(mobileBrand)) {
            buyHuaWei(price, buyResult);
        } else if (MobileBrand.APPLE.getBrand().equals(mobileBrand)) {
            buyApple(price, buyResult);
        } else {
            buyResult.append("手机品牌不存在");
        }

        return buyResult.toString();
    }

    /**
     * 购买苹果手机
     *
     * @param price      价格
     * @param buyResult  结果
     */
    private static void buyApple(Integer price, StringBuilder buyResult) {
        // 苹果品牌,根据价格进行购买逻辑
        if (price <= 5000) {
            buyResult.append("购买苹果手机成功,").append("手机价格: ").append(price).append(" 附赠AirTag");
        } else if (price <= 8000) {
            buyResult.append("购买苹果手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.98D).append(" 12期免息");
        } else {
            buyResult.append("购买苹果手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.95D).append(" 24期免息 附赠MagSafe");
        }
    }

    /**
     * 购买华为手机
     *
     * @param price 手机价格
     * @param buyResult 购买结果
     */
    private static void buyHuaWei(Integer price, StringBuilder buyResult) {
        // 华为品牌,根据价格进行购买逻辑
        if (price <= 1500) {
            buyResult.append("购买华为手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.95D);
        } else if (price <= 3000) {
            buyResult.append("购买华为手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.9D).append(" 附赠FreeBuds SE");
        } else {
            buyResult.append("购买华为手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.85D).append(" 24期免息");
        }
    }

    /**
     * 购买小米手机
     *
     * @param price 手机价格
     * @param buyResult 购买结果
     */
    private static void buyXiaoMi(Integer price, StringBuilder buyResult) {
        // 小米品牌,根据价格进行购买逻辑
        if (price <= 1000) {
            buyResult.append("购买小米手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.98D);
        } else if (price <= 2000) {
            buyResult.append("购买小米手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.95D).append(" 附赠有线耳机");
        } else {
            buyResult.append("购买小米手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.9D).append(" 附赠小米手环");
        }
    }
}

此时已经消除了显性的代码嵌套,但是依旧存在着复杂的if逻辑,并且也是存在着隐性if嵌套,因此我们就可以通过策略模式,来消除一部分的if逻辑,消除这种隐性的if嵌套

3. 优化思路

  1. 当前的逻辑流程图是这样的

可以看出,当前的流程图有着以下特点

  • 在某一个节点会衍生出多种情况
  • 每种情况的逻辑都是类似(判断价格区间,进行对应的购买逻辑)
  • 如果是以代码翻译,就会出现多if else,在代码层面,甚至出现嵌套if,并且难以真正消除if嵌套

  1. 因此我们并不一定说,必须在代码层面就能看出来什么时候需要抽象,通过对流程图进行分析,或许可以更快找到优化方案
  2. 将不同品牌对应的价格区间判断+购买逻辑,抽象成接口:MobileBrandStrategy 然后通过定义不同的实现类:XiaoMiStrategyHuaWeiStrategy等。
  3. 以接口接受不同实现类,这一逻辑通过MobileFactory进行封装,随后只需要调用相关的接口方法,即可获取到对应的购买结果
  4. 通过这样的优化,我们可以将第一层选择:根据不同的品牌选择不同的策略方法,抽象成为类级别,消除了第二层的if。后续再添加其他的业务逻辑(例如添加红米手机的折扣策略),只需要添加类方法,而不需要在原方法内部进行if分支的增加。提高了可拓展性
  5. 因此,策略模式的优势就是可以消除最外层的if条件选择,并且符合开闭原则,也就是说,无需在方法层面上进行代码的更改(即使是添加代码,不删改,也是不符合开闭原则的),只需要添加新的类,修改工厂类中的创建对象逻辑,即可完成业务逻辑的拓展。
  6. 当然,这里依旧有些缺陷,即需要在工厂方法中进行对应的修改,并且这里也涉及到了if逻辑,这种优化是不彻底的,本质上是将最外层的分支选择,抽象成了针对策略类的选择。因此后续我们也会继续讲解针对这里的优化逻辑

4. 进一步改造

  • 策略接口提供
/**
 * 手机品牌策略类,专注于针对不同的手机品牌进行不同的策略方式选择
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
public interface MobileBrandStrategy {

    /**
     * 手机购买策略
     *
     * @param price     手机价格
     * @param buyResult 手机购买结果
     * @return 手机购买结果
     */
    StringBuilder buyMobile(Integer price, StringBuilder buyResult);
}
  • 策略类提供
/**
 * 基于小米品牌实现的策略类
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
@Component
public class XiaoMiStrategy implements MobileBrandStrategy {

    /**
     * 手机购买策略
     *
     * @param price     手机价格
     * @param buyResult 购买结果
     * @return 手机购买结果
     */
    @Override
    public StringBuilder buyMobile(Integer price, StringBuilder buyResult) {
        // 小米品牌,根据价格进行购买逻辑
        if (price <= 1000) {
            buyResult.append("购买小米手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.98D);
        } else if (price <= 2000) {
            buyResult.append("购买小米手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.95D).append(" 附赠有线耳机");
        } else {
            buyResult.append("购买小米手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.9D).append(" 附赠小米手环");
        }

        return buyResult;
    }
}
/**
 * 基于华为品牌实现的策略类
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
@Component
public class HuaWeiStrategy implements MobileBrandStrategy {
    /**
     * 手机购买策略
     *
     * @param price     手机价格
     * @param buyResult 手机购买结果
     * @return 手机购买结果
     */
    @Override
    public StringBuilder buyMobile(Integer price, StringBuilder buyResult) {
        // 华为品牌,根据价格进行购买逻辑
        if (price <= 1500) {
            buyResult.append("购买华为手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.95D);
        } else if (price <= 3000) {
            buyResult.append("购买华为手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.9D).append(" 附赠FreeBuds SE");
        } else {
            buyResult.append("购买华为手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.85D).append(" 24期免息");
        }
        return buyResult;
    }
}
/**
 * 基于苹果品牌的策略方式
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
@Component
public class AppleStrategy implements MobileBrandStrategy {

    /**
     * 手机购买策略
     *
     * @param price     手机价格
     * @param buyResult 手机购买结果
     * @return 手机购买结果
     */
    @Override
    public StringBuilder buyMobile(Integer price, StringBuilder buyResult) {
        // 苹果品牌,根据价格进行购买逻辑
        if (price <= 5000) {
            buyResult.append("购买苹果手机成功,").append("手机价格: ").append(price).append(" 附赠AirTag");
        } else if (price <= 8000) {
            buyResult.append("购买苹果手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.98D).append(" 12期免息");
        } else {
            buyResult.append("购买苹果手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.95D).append(" 24期免息 附赠MagSafe");
        }

        return buyResult;
    }
}
  • 工厂类提供
/**
 * 手机品牌策略工厂类
 * <p>
 * 该工厂类根据传入的手机品牌名称,返回对应的手机品牌策略实现类。
 * 支持的品牌包括:小米、华为、苹果。
 * </p>
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
public class MobileStrategyFactory {

    /**
     * 私有构造函数,防止外部实例化
     */
    private MobileStrategyFactory() {}
    
    /**
     * 根据手机品牌名称获取对应的策略实现类
     * <p>
     * 通过比较传入的品牌名称与枚举类中定义的品牌名称,返回相应的策略对象。
     * 如果没有匹配的品牌,则返回 null。
     * </p>
     *
     * @param mobileBrand 手机品牌名称
     * @return 对应的手机品牌策略实现类,如果未找到则返回 null
     */
    public static MobileBrandStrategy getMobileStrategy(String mobileBrand) {
        if (MobileBrand.XIAOMI.getBrand().equals(mobileBrand)) {
            return new XiaoMiStrategy();
        } else if (MobileBrand.HUAWEI.getBrand().equals(mobileBrand)) {
            return new HuaWeiStrategy();
        } else if (MobileBrand.APPLE.getBrand().equals(mobileBrand)) {
            return new AppleStrategy();
        } else {
            return null;
        }
    }
}
  • 核心类提供
/**
 * 手机服务类
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
@Slf4j
@Service
public class MobileService {

    /**
     * 购买手机
     *
     * @param mobileBrand 手机品牌
     * @param price       手机价格
     * @return 购买结果
     */
    public String buyMobile(String mobileBrand, Integer price) {
        StringBuilder buyResult = new StringBuilder();

        if (price == null) {
            buyResult.append("手机价格不能为空");
            return buyResult.toString();
        }

        MobileBrandStrategy mobileStrategy = MobileStrategyFactory.getMobileStrategy(mobileBrand);
        buyResult = Objects.isNull(mobileStrategy) ? buyResult.append("手机品牌不能为空") : mobileStrategy.buyMobile(price, buyResult);
        return buyResult.toString();
    }
}

5. 改造后总结

  1. 改造后的流程图如下

  1. 优化后,品牌选择逻辑封装到工厂中
  2. 所有策略实现统一接口
  3. 价格区间判断从主流程下沉到各策略内部
  4. 新增品牌只需在策略实现子图中添加节点

6. 最终优化方案

优化思路
  1. 上一次我们提到了工厂中依旧存在着根据if进行不同条件的返回。
  2. 也就是说,当我们新增一个策略类,仍旧需要进行if-else的分支增加。
  3. 换句话说,我们依旧是需要使用if-else的普通映射模式,但是我们可以更换一种方式,让工厂类实现自动注入映射关系。
  4. 也就是说,通过反射+Map,实现一对一映射,从而彻底消除if-else,实现最终优化。
  5. 而SpringBoot可以直接注入一个接口下的所有实现类,只要实现类进行了Bean注册,即可通过构造方法进行自动化注入。
最终优化代码
  • 对应的接口改动
/**
 * 手机品牌策略类,专注于针对不同的手机品牌进行不同的策略方式选择
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
public interface MobileBrandStrategy {

    /**
     * 手机购买策略
     *
     * @param price     手机价格
     * @param buyResult 手机购买结果
     * @return 手机购买结果
     */
    StringBuilder buyMobile(Integer price, StringBuilder buyResult);

    /**
     * 获取手机品牌
     *
     * @return 手机品牌
     */
    String getBrand();
}
  • 其中一个类的改动(其他的类遵循类似改动即可)
/**
 * 基于小米品牌实现的策略类
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
@Data
@Component
public class XiaoMiStrategy implements MobileBrandStrategy {

    private String brand = MobileBrand.XIAOMI.getBrand();

    /**
     * 手机购买策略
     *
     * @param price     手机价格
     * @param buyResult 购买结果
     * @return 手机购买结果
     */
    @Override
    public StringBuilder buyMobile(Integer price, StringBuilder buyResult) {
        // 小米品牌,根据价格进行购买逻辑
        if (price <= 1000) {
            buyResult.append("购买小米手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.98D);
        } else if (price <= 2000) {
            buyResult.append("购买小米手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.95D).append(" 附赠有线耳机");
        } else {
            buyResult.append("购买小米手机成功,").append("手机价格: ").append(Double.valueOf(price) * 0.9D).append(" 附赠小米手环");
        }

        return buyResult;
    }
}
  • 工厂改动(通过Spring以来机制进行注入)
/**
 * 手机品牌策略工厂类
 * <p>
 * 该工厂类根据传入的手机品牌名称,返回对应的手机品牌策略实现类。
 * 支持的品牌包括:小米、华为、苹果。
 * </p>
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
@Component
public class MobileStrategyFactory {

    /**
     * 存储手机品牌与对应策略实现类映射关系的静态Map
     * key: 手机品牌名称
     * value: 对应的手机品牌策略实现类
     */
    private static final Map<String, MobileBrandStrategy> MOBILE_BRAND_STRATEGY_MAP;

    // 静态初始化块,初始化策略映射Map
    static {
        MOBILE_BRAND_STRATEGY_MAP = new HashMap<>();
    }

    /**
     * 构造函数,通过Spring注入的所有手机品牌策略实现类列表来初始化策略映射Map
     *
     * @param mobileBrandStrategies Spring容器中所有实现了MobileBrandStrategy接口的Bean列表
     */
    public MobileStrategyFactory(List<MobileBrandStrategy> mobileBrandStrategies) {
        // 遍历所有手机品牌策略实现类
        for (MobileBrandStrategy mobileBrandStrategy : mobileBrandStrategies) {
            // 获取策略实现类对应的手机品牌
            String brand = mobileBrandStrategy.getBrand();
            // 将手机品牌与对应的策略实现类存入映射Map中
            MOBILE_BRAND_STRATEGY_MAP.put(brand, mobileBrandStrategy);
        }
    }

    /**
     * 根据手机品牌名称获取对应的策略实现类
     * <p>
     * 通过比较传入的品牌名称与已注册的品牌名称,返回相应的策略对象。
     * 如果没有匹配的品牌,则返回 null。
     * </p>
     *
     * @param mobileBrand 手机品牌名称
     * @return 对应的手机品牌策略实现类,如果未找到则返回 null
     */
    public MobileBrandStrategy getMobileStrategy(String mobileBrand) {
        // 从映射Map中获取对应品牌的策略实现类
        return MOBILE_BRAND_STRATEGY_MAP.get(mobileBrand);
    }
}
  • 核心类实现
/**
 * 手机服务类
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MobileService {

    private final MobileStrategyFactory mobileStrategyFactory;

    /**
     * 购买手机
     *
     * @param mobileBrand 手机品牌
     * @param price       手机价格
     * @return 购买结果
     */
    public String buyMobile(String mobileBrand, Integer price) {
        StringBuilder buyResult = new StringBuilder();

        if (price == null) {
            buyResult.append("手机价格不能为空");
            return buyResult.toString();
        }

        MobileBrandStrategy mobileStrategy = mobileStrategyFactory.getMobileStrategy(mobileBrand);
        buyResult = Objects.isNull(mobileStrategy) ? buyResult.append("手机品牌不能为空") : mobileStrategy.buyMobile(price, buyResult);
        return buyResult.toString();
    }
}
对应流程图

7. 总结

  1. 策略模式,是通过将流程图中的多个可选择,且逻辑类似的平行节点抽象成类,不同的策略类负责对应的单一职责,从而消除了显性的内部逻辑。
  2. 又通过工厂模式,封装不同策略类选择的过程,将最外层的节点选择流程进行隐藏,从而实现了基础版本的策略模式。
  3. 而通过Spring的依赖注入机制,将对应的策略接口下的所有策略实现类进行依赖注入,在初始化构造函数中实现好对Map的存储。从而实现对工厂的映射模式的底层改进(if/switch分支选择 ——> Hash映射)
  4. 通过这种修改,实现了将复杂的分支选择,转化为Hash映射,以及对应的分支的逻辑,下沉到对应的实现类中,如果要拓展新的分支方法,只需要添加新方法,符合开闭原则,大大提高了代码的可维护性以及可拓展性。

2. 函数级策略模式

1. 场景描述

同1.1一样,我们依旧是通过这种形式来进行讲述,并进行函数级别优化,最后我们进行对比,说明这两种策略模式都适合在什么时候进行使用。

2. 优化思路

  1. 当前传统的策略模式,其主要思路就是通过将具有相似逻辑的平行节点,抽象成一个接口,再对应每个节点进行对应实现。并通过工厂模式,使用Map形式进行O(1)级别的映射。
  2. 但是有一个问题就是,当流程几乎固定,且使用次数不超过三次,以及对应的平行节点不断增加,那么对应的策略类也会发生膨胀,而其内部的逻辑几乎是固定的,这也就导致加载过程中,要进行大量的类级别加载,而内部逻辑流程固定,会产生资源浪费。
  3. 针对这一种情况,我们可以进一步进行抽象,将类级别的抽象,换成函数级别的抽象,当流程几乎固定不变,且不会很复杂的时候,函数级策略模式就起到了更佳的优势。
  4. 针对我们当前的场景:每个品牌的折扣方式几乎固定(都是分阶段进行不同折扣,以及不同的附加优惠)。那么我们可以在传统策略实现的基础上,进一步进行抽象。
  5. 三个手机品牌会对价格进行区间定位,根据不同的区间定位,实现不同的折扣策略。也就是说,我们可以抽象出来一个函数接口列表,返回值为Boolean,根据其满足的区间,进行不同级别的折扣策略。
  6. 新版流程图如下

3. 对应代码实现

  • 规则类实现(相似流程,函数级别抽象,更轻量)
/**
 * 手机品牌折扣规则类,通过函数式接口实现策略模式的轻量级替换
 * 该类封装了不同品牌的折扣规则,支持动态匹配和计算折扣价格
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
@Data
@AllArgsConstructor
public class MobileBrandAccountRule {
    /**
     * 折扣条件判断函数列表,每个函数接收价格参数,返回是否满足折扣条件
     */
    private List<Function<Integer, Boolean>> isAccounts;
    
    /**
     * 折扣率列表,与isAccounts一一对应,表示满足条件时的价格折扣倍数
     */
    private List<Double> accounts;
    
    /**
     * 优惠结果描述列表,与isAccounts一一对应,表示满足条件时的优惠说明
     */
    private List<String> accountResults;
    
    /**
     * 手机品牌名称
     */
    private String mobileBrand;
    
    /**
     * 根据手机价格和折扣规则计算购买结果
     * 遍历所有折扣规则,找到第一个满足条件的规则并应用折扣
     *
     * @param price 手机原价
     * @param sb    结果构建器,用于拼接购买结果信息
     * @return 包含购买结果信息的StringBuilder对象
     */
    public StringBuilder buyMobile(Integer price, StringBuilder sb) {
        int size = isAccounts.size();
        for (int i = 0; i < size; i++) {
            Function<Integer, Boolean> isAccount = isAccounts.get(i);
            Boolean inAccount = isAccount.apply(price);
            if (inAccount) {
                sb.append("手机购买成功, ")
                        .append("手机价格: ")
                        .append(Double.valueOf(price) * accounts.get(i))
                        .append(",")
                        .append("优惠附加: ")
                        .append(accountResults.get(i));
                return sb;
            }
        }
        return sb.append("手机购买失败");
    }
}
  • 工厂封装(需要添加实现类,只需要在工厂中添加对应的实现方法)
/**
 * 手机品牌折扣规则工厂类
 * 该工厂负责创建和管理不同品牌的折扣规则,通过策略模式实现规则的灵活扩展
 * 如果要添加新品牌实现,只需要在工厂中添加对应的实现方法
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
public class MobileBrandAccountRuleFactory {
    
    /**
     * 手机品牌折扣规则缓存
     */
    private MobileBrandAccountRuleFactory() {}

    /**
     * 存储各品牌折扣规则的映射表
     * key: 品牌名称
     * value: 对应的折扣规则对象
     */
    private static final Map<String, MobileBrandAccountRule> MOBILE_BRAND_ACCOUNT_RULE_MAP;

    static {
        // 初始化各品牌折扣规则映射表
        MOBILE_BRAND_ACCOUNT_RULE_MAP = Map.of(
                // 小米品牌折扣规则配置
                MobileBrand.XIAOMI.getBrand(),
                new MobileBrandAccountRule(
                        // 价格区间判断条件列表
                        List.of(
                                // 价格小于等于1000元
                                price -> price <= 1000,
                                // 价格小于等于2000元
                                price -> price <= 2000,
                                // 价格大于2000元
                                price -> price > 2000
                        ),
                        // 对应的折扣率列表
                        List.of(
                                // 98折
                                0.98D,
                                // 95折
                                0.95D,
                                // 92折
                                0.92D
                        ),
                        // 对应的优惠赠品列表
                        List.of(
                                // 无赠品
                                "",
                                // 赠送有线耳机
                                "赠有线耳机",
                                // 赠送小米手环
                                "赠小米手环"
                        ),
                        MobileBrand.XIAOMI.getBrand()
                ),
                // 华为品牌折扣规则配置
                MobileBrand.HUAWEI.getBrand(),
                new MobileBrandAccountRule(
                        // 价格区间判断条件列表
                        List.of(
                                // 价格小于等于1500元
                                price -> price <= 1500,
                                // 价格小于等于3000元
                                price -> price <= 3000,
                                // 价格大于3000元
                                price -> price > 3000
                        ),
                        // 对应的折扣率列表
                        List.of(
                                // 98折
                                0.98D,
                                // 95折
                                0.95D,
                                // 92折
                                0.92D
                        ),
                        // 对应的优惠赠品列表
                        List.of(
                                // 无赠品
                                "",
                                // 赠送FreeBuds SE耳机
                                "赠FreeBuds SE",
                                // 提供24期免息分期
                                "24期免息"
                        ),
                        MobileBrand.HUAWEI.getBrand()
                ),
                // 苹果品牌折扣规则配置
                MobileBrand.APPLE.getBrand(),
                new MobileBrandAccountRule(
                        // 价格区间判断条件列表
                        List.of(
                                // 价格小于等于5000元
                                price -> price <= 5000,
                                // 价格小于等于8000元
                                price -> price <= 8000,
                                // 价格大于8000元
                                price -> price > 8000
                        ),
                        // 对应的折扣率列表
                        List.of(
                                // 无折扣
                                1.00D,
                                // 98折
                                0.98D,
                                // 95折
                                0.95D
                        ),
                        // 对应的优惠赠品列表
                        List.of(
                                // 赠送AirTag
                                "赠AirTag",
                                // 提供12期免息分期
                                "12期免息",
                                // 提供24期免息分期并赠送MagSafe
                                "24期免息 + 赠MagSafe"
                        ),
                        MobileBrand.APPLE.getBrand()
                )
        );
    }
    
    
    /**
     * 根据品牌名称获取对应的折扣规则
     *
     * @param brand 品牌名称
     * @return 对应的折扣规则对象,如果找不到则返回null
     */
    public static MobileBrandAccountRule getMobileBrandAccountRule(String brand) {
        return MOBILE_BRAND_ACCOUNT_RULE_MAP.get(brand);
    }
}
  • 核心类使用(几乎无改动)
/**
 * 手机服务类
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MobileService {

    /**
     * 购买手机
     *
     * @param mobileBrand 手机品牌
     * @param price       手机价格
     * @return 购买结果
     */
    public String buyMobile(String mobileBrand, Integer price) {
        StringBuilder buyResult = new StringBuilder();

        if (price == null) {
            buyResult.append("手机价格不能为空");
            return buyResult.toString();
        }

        MobileBrandAccountRule mobileBrandAccountRule = MobileBrandAccountRuleFactory.getMobileBrandAccountRule(mobileBrand);
        buyResult = Objects.isNull(mobileBrandAccountRule) ? buyResult.append("手机品牌不能为空") : mobileBrandAccountRule.buyMobile(price, buyResult);
        return buyResult.toString();
    }
}

4. 当前示例与传统策略模式对比

场景

函数级策略模式

传统策略模式

补充

实现复杂度

该示例中,函数级策略模式需要抽象设计编排流程,而传统策略模式只需要按照不同的策略方式直接写代码即可

加载消耗

函数级策略模式不需要进行策略类加载,而传统策略模式如果类膨胀,性能消耗明显

可拓展性

函数级策略模式中,如果需要添加新的策略,只需要在工厂的初始化中直接添加即可

传统策略模式中,如果需要添加新的策略,只需要再创建新的实现类即可

简洁性

函数级策略模式只需要针对函数级别进行不同的输入即可

而传统策略模式则需要构建完整的类,代码两略多

总结来看,当前场景下,二者其实相差无几,是因为当前的场景的特点如下:

  1. 流程略复杂但是固定
  2. 流程可以抽象(区间定位)
  3. 后续的处理较为简单(进行打折,输出结果)

这些情况,正好处于二者适用范围的交界处,因此二者会展现出来不相上下的情况。

但是当情况略有改动,二者的区分就很明显了。

5. 换一个例子

前言介绍

场景描述:通过Redis的info memory命令,获取到对应的内存信息。实现性能监控。针对于某些重要的属性,进行阈值判断,如果超过阈值,就加入到字符串中作为警告。

简单分析:当前场景,如果要实现,会有多个if,但是并不会出现上述的if-else,这是否也可以通过策略类实现?是可以的,其有十分明显的一个特点:多个if的执行顺序可以替换,也就是说,其节点是平行的,而且是允许多个节点同时进行。

换句话说,传统策略模式,是多个节点中选择单个节点执行。而当前场景,则是多个节点中可以选择任意多个节点执行。不过其不再适合通过工厂模式返回对应的单个策略类,而是可以通过策略类内部的条件判断,进行过滤、执行。

也就是说:策略模式并不是死板的,而是灵活多变,判断时机,就是多节点平行,再通过单节点执行还是任意多节点执行,进行进一步选择。而具体到选择函数级策略还是传统的,就需要再次细分。

原始代码
/**
 * 监控告警
 * 返回Redis内存监控指标的告警信息
 *
 * @return 告警信息
 */
@Override
public String monitorAlerts() {
    StringBuilder sb = new StringBuilder();
    int memoryPercent = queryUsedMemoryPercent();
    double memFragmentationRatio = calculateFragmentationRatio();
    long evictedKeys = getEvictedKeysCount();
    double fragmentationRisk = calculateFragmentationRiskLevel();
    double evictionRisk = calculateEvictionRiskIndicator();
    
    if (memoryPercent > MEMORY_PERCENT_THRESHOLD) {
        sb.append(sb.isEmpty() ? "" : ", ").append("内存使用率过高: ").append(memoryPercent);
    }
    
    if (memFragmentationRatio > MEM_FRAGMENTATION_RATIO_THRESHOLD) {
        sb.append(sb.isEmpty() ? "" : ", ").append("内存碎片率过高: ").append(memFragmentationRatio);
    }
    
    if (evictedKeys > 0) {
        sb.append(sb.isEmpty() ? "" : ", ").append("键驱逐数量: ").append(evictedKeys);
    }
    
    if (fragmentationRisk > FRAGMENTATION_RISK_THRESHOLD) {
        sb.append(sb.isEmpty() ? "" : ", ").append("内存碎片风险等级: ").append(fragmentationRisk);
    }
    
    if (evictionRisk > EVICTION_RISK_THRESHOLD) {
        sb.append(sb.isEmpty() ? "" : ", ").append("内存驱逐风险等级: ").append(evictionRisk);
    }
    
    return sb.toString();
}
流程图

6. 针对新例子的优化

我们可以看出,这个方法中的多if,是可以调换,在流程图中展现的形式,就是平行,可以根据条件,选择任意多节点同时执行。但是这里会显得臃肿,因为if很多,规则类似,导致复用性差。

因此我们依旧可以使用策略模式进行优化,而这里我们使用函数级进行策略模式进行优化,方法级别和函数级别转换其实也十分容易。根据创建的函数,与对应的方法进行转换。这里就需要考验开发者的代码能力,如何将二者转换。这里上述有对应示例(类级—>方法级),可以参考一下。

  • 核心类实现
/**
 * 简单规则+message的整合类
 *
 * @author 王玉涛
 * @version 1.0
 * @since 2025/8/12
 */
@Data
@AllArgsConstructor
public class AlertRule {
    /**
     * 告警判断条件,通过Supplier函数式接口实现动态判断
     * 返回true表示触发告警,false表示不触发告警
     */
    private Supplier<Boolean> isAlert;

    /**
     * 告警信息内容,当告警条件满足时需要输出的信息
     */
    private String message;

    /**
     * 检查并展示告警信息
     * 当告警条件满足时,将告警信息追加到StringBuilder中
     * 如果StringBuilder中已有内容,则先添加逗号分隔符
     * 
     * @param sb 用于收集告警信息的StringBuilder对象,不可为null
     */
    public void showAlert(StringBuilder sb) {
        if (Boolean.TRUE.equals(isAlert.get())) {
            sb.append(sb.isEmpty() ? "" : ",");
            sb.append(message);
        }
    }
}
  • 对应方法优化
/**
 * 监控告警
 * 返回Redis内存监控指标的告警信息
 *
 * @return 告警信息
 */
@Override
public String monitorAlerts() {
    // 创建StringBuilder用于收集告警信息
    StringBuilder sb = new StringBuilder();
    
    // 获取各项监控指标值
    int memoryPercent = queryUsedMemoryPercent();                   
    double fragmentationRisk = calculateFragmentationRiskLevel();   
    double evictionRisk = calculateEvictionRiskIndicator();         
    
    // 定义告警规则列表,每个规则包含判断条件和告警信息
    List<AlertRule> alertRules = Arrays.asList(
            // 内存使用率过高告警:当内存使用百分比超过阈值时触发
            new AlertRule(() -> memoryPercent > MEMORY_PERCENT_THRESHOLD, "内存使用率过高: " + memoryPercent),
            // 内存碎片风险告警:当内存碎片比率超过阈值时触发
            new AlertRule(() -> memFragmentationRatio > MEM_FRAGMENTATION_RATIO_THRESHOLD, "内存碎片风险: " + memFragmentationRatio),
            // 内存驱逐告警:当存在因内存不足被驱逐的键时触发
            new AlertRule(() -> evictedKeys > 0, "存在因内存不足被驱逐的键数量: " + evictedKeys),
            // 内存碎片风险等级过高告警:当碎片风险等级超过阈值时触发
            new AlertRule(() -> fragmentationRisk > FRAGMENTATION_RISK_THRESHOLD, "内存碎片风险等级过高: " + fragmentationRisk),
            // 内存驱逐风险等级过高告警:当驱逐风险等级超过阈值时触发
            new AlertRule(() -> evictionRisk > EVICTION_RISK_THRESHOLD, "内存驱逐风险等级过高: " + evictionRisk)
    );

    // 遍历告警规则,筛选出满足条件的规则并执行告警信息收集
    alertRules.stream()
            // 筛选触发告警的规则
            .filter(alertRule -> alertRule.getIsAlert().get())
            // 执行告警信息收集
            .forEach(alertRule -> alertRule.showAlert(sb));  
    
    // 返回最终的告警信息字符串
    return sb.isEmpty() ? "监控指标正常!" : sb.toString();
}

通过以上优化,我们成功将多个平行if消除,并且提升了拓展性,针对这种复用性不高、逻辑类似、规则简单的情况下,对比传统的策略模式有着碾压级别优势。因为其更加轻量,更加适合当前业务场景。

3. 总结

策略模式是消除if的利器,但是我们要明白一个原则,不要为了炫技而胡乱使用策略模式,因为设计模式通常都是重量级的,并且也会在一定程度上,降低可读性,需要一定的技术门槛。如果使用不当,就会徒增复杂度。因此使用策略模式之前,我们需要权衡一下:使用策略模式消除if后,其带来的收益(可维护性,可拓展性)是否超过了引入设计模式而带来的复杂度提升。

  1. 简单来说,策略模式的引用时机,就是当一个流程中,出现了平行的多节点,且多节点的逻辑相似。我们可以考虑引入设计模式。
  2. 当我们考虑引入设计模式后,需要观察该平行节点,是可以多节点并行,还是单节点串行(是if-else-if,还是纯if),通过这一点,我们考虑是通过工厂模式+Map一对一映射来完成,还是通过stream流+filter进行多节点同时进行
  3. 进一步,我们也需要判断当前的节点规模,当明确会<3个,且不会增长时候,不推荐使用策略模式。而当节点不少于3个,且逻辑可以抽象时候,推荐使用策略模式。
  4. 然后再根据节点内部的复杂度,考虑是用传统策略模式,还是使用函数级策略模式。当流程相对固定,可以进行抽象提取时候,推荐使用函数级别策略模式,其虽然会考验开发者的抽象能力+代码设计能力。但是在这种轻量级别场景对传统方式有着碾压级别优势。而当内部逻辑复杂,几乎无法提取共性时候,就推荐传统策略模式。
  5. 这两种策略模式并不是互斥,而是在轻量级和重量级场景中形成互补。通过这一套方法论,即使是开发初期,开发者也可以迅速判断当前情景是否适合采用策略模式。适合什么级别的设计模式。从而直接提高项目的可维护性。
  6. 决策流程图如下

The End!

#策略模式详解##Java##offer比较#
全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务