Java策略模式|规则对象化
前言
- Java策略模式是经常使用的一个设计模式,其通过规则驱动,将不同的规则以及不同的实现逻辑,通过封装成类,实现消除复杂的平行规则。
- 而传统的策略模式属于类级别抽象,一个规则会抽象成类级别,策略模式虽然不复杂,但是很笨重。
- 而轻量级的策略模式,则把类级别抽象换为函数级别抽象,将相似的规则、相似的逻辑一同封装进一个函数接口中,通过Lambda表达式进行不同规则的编写。
- 这种函数级策略模式,是即时创建、即时使用、即时删除的,避免了传统策略模式导致的类爆炸,同时又沿用了策略模式的高可拓展、高可维护性。
- 函数级策略模式在特定场景下,对传统策略模式有着碾压级别的优势,但是并不意味着传统策略模式会被替代,在特定的情况下,其依旧是消除代码大规模显性规则的利器。(例如if膨胀问题)
Let's Start!
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. 优化思路
- 当前的逻辑流程图是这样的
可以看出,当前的流程图有着以下特点
- 在某一个节点会衍生出多种情况
- 每种情况的逻辑都是类似(判断价格区间,进行对应的购买逻辑)
- 如果是以代码翻译,就会出现多if else,在代码层面,甚至出现嵌套if,并且难以真正消除if嵌套
- 因此我们并不一定说,必须在代码层面就能看出来什么时候需要抽象,通过对流程图进行分析,或许可以更快找到优化方案
- 将不同品牌对应的价格区间判断+购买逻辑,抽象成接口:MobileBrandStrategy 然后通过定义不同的实现类:XiaoMiStrategyHuaWeiStrategy等。
- 以接口接受不同实现类,这一逻辑通过MobileFactory进行封装,随后只需要调用相关的接口方法,即可获取到对应的购买结果
- 通过这样的优化,我们可以将第一层选择:根据不同的品牌选择不同的策略方法,抽象成为类级别,消除了第二层的if。后续再添加其他的业务逻辑(例如添加红米手机的折扣策略),只需要添加类方法,而不需要在原方法内部进行if分支的增加。提高了可拓展性
- 因此,策略模式的优势就是可以消除最外层的if条件选择,并且符合开闭原则,也就是说,无需在方法层面上进行代码的更改(即使是添加代码,不删改,也是不符合开闭原则的),只需要添加新的类,修改工厂类中的创建对象逻辑,即可完成业务逻辑的拓展。
- 当然,这里依旧有些缺陷,即需要在工厂方法中进行对应的修改,并且这里也涉及到了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. 改造后总结
- 改造后的流程图如下
- 优化后,品牌选择逻辑封装到工厂中
- 所有策略实现统一接口
- 价格区间判断从主流程下沉到各策略内部
- 新增品牌只需在策略实现子图中添加节点
6. 最终优化方案
优化思路
- 上一次我们提到了工厂中依旧存在着根据if进行不同条件的返回。
- 也就是说,当我们新增一个策略类,仍旧需要进行if-else的分支增加。
- 换句话说,我们依旧是需要使用if-else的普通映射模式,但是我们可以更换一种方式,让工厂类实现自动注入映射关系。
- 也就是说,通过反射+Map,实现一对一映射,从而彻底消除if-else,实现最终优化。
- 而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. 总结
- 策略模式,是通过将流程图中的多个可选择,且逻辑类似的平行节点抽象成类,不同的策略类负责对应的单一职责,从而消除了显性的内部逻辑。
- 又通过工厂模式,封装不同策略类选择的过程,将最外层的节点选择流程进行隐藏,从而实现了基础版本的策略模式。
- 而通过Spring的依赖注入机制,将对应的策略接口下的所有策略实现类进行依赖注入,在初始化构造函数中实现好对Map的存储。从而实现对工厂的映射模式的底层改进(if/switch分支选择 ——> Hash映射)
- 通过这种修改,实现了将复杂的分支选择,转化为Hash映射,以及对应的分支的逻辑,下沉到对应的实现类中,如果要拓展新的分支方法,只需要添加新方法,符合开闭原则,大大提高了代码的可维护性以及可拓展性。
2. 函数级策略模式
1. 场景描述
同1.1一样,我们依旧是通过这种形式来进行讲述,并进行函数级别优化,最后我们进行对比,说明这两种策略模式都适合在什么时候进行使用。
2. 优化思路
- 当前传统的策略模式,其主要思路就是通过将具有相似逻辑的平行节点,抽象成一个接口,再对应每个节点进行对应实现。并通过工厂模式,使用Map形式进行O(1)级别的映射。
- 但是有一个问题就是,当流程几乎固定,且使用次数不超过三次,以及对应的平行节点不断增加,那么对应的策略类也会发生膨胀,而其内部的逻辑几乎是固定的,这也就导致加载过程中,要进行大量的类级别加载,而内部逻辑流程固定,会产生资源浪费。
- 针对这一种情况,我们可以进一步进行抽象,将类级别的抽象,换成函数级别的抽象,当流程几乎固定不变,且不会很复杂的时候,函数级策略模式就起到了更佳的优势。
- 针对我们当前的场景:每个品牌的折扣方式几乎固定(都是分阶段进行不同折扣,以及不同的附加优惠)。那么我们可以在传统策略实现的基础上,进一步进行抽象。
- 三个手机品牌会对价格进行区间定位,根据不同的区间定位,实现不同的折扣策略。也就是说,我们可以抽象出来一个函数接口列表,返回值为Boolean,根据其满足的区间,进行不同级别的折扣策略。
- 新版流程图如下
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. 当前示例与传统策略模式对比
场景 | 函数级策略模式 | 传统策略模式 | 补充 |
实现复杂度 | 高 | 低 | 该示例中,函数级策略模式需要抽象设计编排流程,而传统策略模式只需要按照不同的策略方式直接写代码即可 |
加载消耗 | 低 | 高 | 函数级策略模式不需要进行策略类加载,而传统策略模式如果类膨胀,性能消耗明显 |
可拓展性 | 高 | 高 | 函数级策略模式中,如果需要添加新的策略,只需要在工厂的初始化中直接添加即可 传统策略模式中,如果需要添加新的策略,只需要再创建新的实现类即可 |
简洁性 | 高 | 中 | 函数级策略模式只需要针对函数级别进行不同的输入即可 而传统策略模式则需要构建完整的类,代码两略多 |
总结来看,当前场景下,二者其实相差无几,是因为当前的场景的特点如下:
- 流程略复杂但是固定
- 流程可以抽象(区间定位)
- 后续的处理较为简单(进行打折,输出结果)
这些情况,正好处于二者适用范围的交界处,因此二者会展现出来不相上下的情况。
但是当情况略有改动,二者的区分就很明显了。
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后,其带来的收益(可维护性,可拓展性)是否超过了引入设计模式而带来的复杂度提升。
- 简单来说,策略模式的引用时机,就是当一个流程中,出现了平行的多节点,且多节点的逻辑相似。我们可以考虑引入设计模式。
- 当我们考虑引入设计模式后,需要观察该平行节点,是可以多节点并行,还是单节点串行(是if-else-if,还是纯if),通过这一点,我们考虑是通过工厂模式+Map一对一映射来完成,还是通过stream流+filter进行多节点同时进行
- 进一步,我们也需要判断当前的节点规模,当明确会<3个,且不会增长时候,不推荐使用策略模式。而当节点不少于3个,且逻辑可以抽象时候,推荐使用策略模式。
- 然后再根据节点内部的复杂度,考虑是用传统策略模式,还是使用函数级策略模式。当流程相对固定,可以进行抽象提取时候,推荐使用函数级别策略模式,其虽然会考验开发者的抽象能力+代码设计能力。但是在这种轻量级别场景对传统方式有着碾压级别优势。而当内部逻辑复杂,几乎无法提取共性时候,就推荐传统策略模式。
- 这两种策略模式并不是互斥,而是在轻量级和重量级场景中形成互补。通过这一套方法论,即使是开发初期,开发者也可以迅速判断当前情景是否适合采用策略模式。适合什么级别的设计模式。从而直接提高项目的可维护性。
- 决策流程图如下