彻底消灭if-else嵌套
一、背景1.1 反面教材
if (true) { if (true) { if (true) { if (true) { if (true) { if (true) { } } } } }}
2.2 亲历的重构
public Double commonMethod(Integer type, Double amount) { if (3 == type) { // 计算费用 if (true) { // 此处省略200行代码,包含n个if-else,下同。。。 } return 0.00; } else if (2 == type) { // 计算费用 return 6.66; }else if (1 == type) { // 计算费用 return 8.88; }else if (0 == type){ return 9.99; } throw new IllegalArgumentException("please input right value");}
2.3 追根溯源
- 我们来分析下代码多分支的原因
- 业务判断
- 空值判断
- 状态判断
- 如何处理呢?
- 在有多种算法相似的情况下,利用策略模式,把业务判断消除,各子类实现同一个接口,只关注自己的实现(本文核心);
- 尽量把所有空值判断放在外部完成,内部传入的变量由外部接口保证不为空,从而减少空值判断(可参考如何从 if-else 的参数校验中解放出来?);
- 把分支状态信息预先缓存在Map里,直接get获取具体值,消除分支(本文也有体现)。
- 来看看简化后的业务调用
CalculationUtil.getFee(type, amount)
serviceFeeHolder.getFee(type, amount)
二、通用部分2.1 需求概括
2.2 会员枚举
用于维护会员类型。
public enum MemberEnum { ORDINARY_MEMBER(0, "普通会员"), JUNIOR_MEMBER(1, "初级会员"), INTERMEDIATE_MEMBER(2, "中级会员"), SENIOR_MEMBER(3, "高级会员"), ; int code; String desc; MemberEnum(int code, String desc) { this.code = code; this.desc = desc; } public int getCode() { return code; } public void setDesc(int code) { this.code = code; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; }}2.3 定义一个策略接口
- compute(Double amount):各计费规则的抽象
- getType():获取枚举中维护的会员级别
public interface FeeService { /** * 计费规则 * @param amount 会员的交易金额 * @return */ Double compute(Double amount); /** * 获取会员级别 * @return */ Integer getType();}三、非框架实现3.1 项目依赖<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope></dependency>3.2 不同计费规则的实现
- 普通会员计费规则
public class OrdinaryMember implements FeeService { /** * 计算普通会员所需缴费的金额 * @param amount 会员的交易金额 * @return */ @Override public Double compute(Double amount) { // 具体的实现根据业务需求修改 return 9.99; } @Override public Integer getType() { return MemberEnum.ORDINARY_MEMBER.getCode(); }}
- 初级会员计费规则
public class JuniorMember implements FeeService { /** * 计算初级会员所需缴费的金额 * @param amount 会员的交易金额 * @return */ @Override public Double compute(Double amount) { // 具体的实现根据业务需求修改 return 8.88; } @Override public Integer getType() { return MemberEnum.JUNIOR_MEMBER.getCode(); }}
- 中级会员计费规则
public class IntermediateMember implements FeeService { /** * 计算中级会员所需缴费的金额 * @param amount 会员的交易金额 * @return */ @Override public Double compute(Double amount) { // 具体的实现根据业务需求修改 return 6.66; } @Override public Integer getType() { return MemberEnum.INTERMEDIATE_MEMBER.getCode(); }}
- 高级会员计费规则
public class SeniorMember implements FeeService { /** * 计算高级会员所需缴费的金额 * @param amount 会员的交易金额 * @return */ @Override public Double compute(Double amount) { // 具体的实现根据业务需求修改 return 0.01; } @Override public Integer getType() { return MemberEnum.SENIOR_MEMBER.getCode(); }}3.3 核心工厂
public class ServiceFeeFactory { private Map<Integer, FeeService> map; public ServiceFeeFactory() { // 该工厂管理所有的策略接口实现类 List<FeeService> feeServices = new ArrayList<>(); feeServices.add(new OrdinaryMember()); feeServices.add(new JuniorMember()); feeServices.add(new IntermediateMember()); feeServices.add(new SeniorMember()); // 把所有策略实现的集合List转为Map map = new ConcurrentHashMap<>(); for (FeeService feeService : feeServices) { map.put(feeService.getType(), feeService); } } /** * 静态内部类单例 */ public static class Holder { public static ServiceFeeFactory instance = new ServiceFeeFactory(); } /** * 在构造方法的时候,初始化好 需要的 ServiceFeeFactory * @return */ public static ServiceFeeFactory getInstance() { return Holder.instance; } /** * 根据会员的级别type 从map获取相应的策略实现类 * @param type * @return */ public FeeService get(Integer type) { return map.get(type); }}3.4 工具类
public class CalculationUtil { /** * 暴露给用户的的计算方法 * @param type 会员级别标示(参见 MemberEnum) * @param money 当前交易金额 * @return 该级别会员所需缴纳的费用 * @throws IllegalArgumentException 会员级别输入错误 */ public static Double getFee(int type, Double money) { FeeService strategy = ServiceFeeFactory.getInstance().get(type); if (strategy == null) { throw new IllegalArgumentException("please input right value"); } return strategy.compute(money); }}
3.5 测试public class DemoTest { @Test public void test() { Double fees = upMethod(1,20000.00); System.out.println(fees); // 会员级别超范围,抛 IllegalArgumentException Double feee = upMethod(5, 20000.00); } public Double upMethod(Integer type, Double amount) { // getFee()是暴露给用户的的计算方法 return CalculationUtil.getFee(type, amount); }}
- 执行结果
8.88java.lang.IllegalArgumentException: please input right value四、Spring Boot 实现
上述方法无非是借助策略模式+工厂模式+单例模式实现,但是实际场景中,我们都已经集成了Spring Boot,这一段就看一下如何借助Spring Boot更简单实现本次的优化。
4.1 项目依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>4.2 不同计费规则的实现
- 普通会员计费规则
@Componentpublic class OrdinaryMember implements FeeService { /** * 计算普通会员所需缴费的金额 * @param amount 会员的交易金额 * @return */ @Override public Double compute(Double amount) { // 具体的实现根据业务需求修改 return 9.99; } @Override public Integer getType() { return MemberEnum.ORDINARY_MEMBER.getCode(); }}
- 初级会员计费规则
@Componentpublic class JuniorMember implements FeeService { /** * 计算初级会员所需缴费的金额 * @param amount 会员的交易金额 * @return */ @Override public Double compute(Double amount) { // 具体的实现根据业务需求修改 return 8.88; } @Override public Integer getType() { return MemberEnum.JUNIOR_MEMBER.getCode(); }}
- 中级会员计费规则
@Componentpublic class IntermediateMember implements FeeService { /** * 计算中级会员所需缴费的金额 * @param amount 会员的交易金额 * @return */ @Override public Double compute(Double amount) { // 具体的实现根据业务需求修改 return 6.66; } @Override public Integer getType() { return MemberEnum.INTERMEDIATE_MEMBER.getCode(); }}
- 高级会员计费规则
@Componentpublic class SeniorMember implements FeeService { /** * 计算高级会员所需缴费的金额 * @param amount 会员的交易金额 * @return */ @Override public Double compute(Double amount) { // 具体的实现根据业务需求修改 return 0.01; } @Override public Integer getType() { return MemberEnum.SENIOR_MEMBER.getCode(); }}4.3 别名转换
思考:程序如何通过一个标识,怎么识别解析这个标识,找到对应的策略实现类?
- application.yml
alias: aliasMap: first: ordinaryMember second: juniorMember third: intermediateMember fourth: seniorMember
- AliasEntity.java
@Component@EnableConfigurationProperties@ConfigurationProperties(prefix = "alias")public class AliasEntity { private HashMap<String, String> aliasMap; public HashMap<String, String> getAliasMap() { return aliasMap; } public void setAliasMap(HashMap<String, String> aliasMap) { this.aliasMap = aliasMap; } /** * 根据描述获取该会员对应的别名 * @param desc * @return */ public String getEntity(String desc) { return aliasMap.get(desc); }}
该类为了便于读取配置,因为存入的是Map的key-value值,key存的是描述,value是各级别会员Bean的别名。
4.4 策略工厂@Componentpublic class ServiceFeeHolder { /** * 将 Spring 中所有实现 ServiceFee 的接口类注入到这个Map中 */ @Resource private Map<String, FeeService> serviceFeeMap; @Resource private AliasEntity aliasEntity; /** * 获取该会员应当缴纳的费用 * @param desc 会员标志 * @param money 交易金额 * @return * @throws IllegalArgumentException 会员级别输入错误 */ public Double getFee(String desc, Double money) { return getBean(desc).compute(money); } /** * 获取会员标志(枚举中的数字) * @param desc 会员标志 * @return * @throws IllegalArgumentException 会员级别输入错误 */ public Integer getType(String desc) { return getBean(desc).getType(); } private FeeService getBean(String type) { // 根据配置中的别名获取该策略的实现类 FeeService entStrategy = serviceFeeMap.get(aliasEntity.getEntity(type)); if (entStrategy == null) { // 找不到对应的策略的实现类,抛出异常 throw new IllegalArgumentException("please input right value"); } return entStrategy; }}
- 将 Spring中所有 ServiceFee.java 的实现类注入到Map中,不同策略通过其不同的key获取其实现类;
- 找不到对应的策略的实现类,抛出IllegalArgumentException异常。
4.5 测试@SpringBootTest@RunWith(SpringRunner.class)public class DemoTest { @Resource ServiceFeeHolder serviceFeeHolder; @Test public void test() { // 计算应缴纳费用 System.out.println(serviceFeeHolder.getFee("second", 1.333)); // 获取会员标志 System.out.println(serviceFeeHolder.getType("second")); // 会员描述错误,抛 IllegalArgumentException System.out.println(serviceFeeHolder.getType("zero")); }}
- 执行结果
8.881java.lang.IllegalArgumentException: please input right value五、总结
- 系统中有很多类,而他们的区别仅仅在于他们的行为不同。
- 一个系统需要动态地在几种算法中选择一种。
5.1 策略模式角色
- Context: 环境类
Context叫做上下文角色,起承上启下封装作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化,对应本文的ServiceFeeFactory.java。
- Strategy: 抽象策略类
定义算法的接口,对应本文的FeeService.java。
- ConcreteStrategy: 具体策略类
实现具体策略的接口,对应本文的OrdinaryMember.java/JuniorMember.java/IntermediateMember.java/SeniorMember.java。