消灭if-else嵌套

消灭if-else嵌套

一、产生原因

1.1 业务判断

  • 在有多种算法相似的情况下,利用策略模式,把业务判断清除,各子类实现同一个接口,只关注自己的实现

1.2 空值判断

1.3 状态判断

  • 把分支状态信息预先缓存在Map里,直接get获取具体指,消除分支。

二、通用部分

2.1 需求概括

​ 有不同的等级:普通、初级、中级、高级、级别不同对应的服务不同

2.2 等级枚举

用于维护等级类型

public enum LevelEnum {
    ORDINARY_LEVE(0, "普通等级"),
    JUNIOR_LEVE(1, "初级等级"),
    INTERMEDIATE_LEVE(2, "中级等级"),
    SENIOR_LEVE(3, "高级等级"),
    ;
    int code;
    String desc;
    LevelEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    // TODO get/set
}

定义一个策略接口

  • 包含两个方法

    • compute(Double amount):各计费规则的抽象
    • getType():获取枚举中维护的等级
    public interface FeeService {
    
        Double compute(Double amount);
    
        Integer getType();
    }
    
    

三、非框架实现

借助策略模式+工厂模式+单例模式实现

3.1 项目依赖

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

3.2 不同计费规则的实现

​ 四个子类实现了策略接口,其中compute()方法实现各个等级的计费逻辑,getType()指定了该类所属的等级。

public class OrdinaryLevel implements FeeService {
    @Override
    public Double compute(Double amount) {
        // 具体的实现根据业务需求修改
        return 9.99;
    }

    @Override
    public Integer getType() {
        return LevelEnum.ORDINARY_LEVEL.getCode();
    }

}

3.3 核心工厂

​ 创建一个工厂类ServiceFeeFactory.java,该工厂类管理所有的策略接口实现类。具体见代码注释。

public class ServiceFeeFactory {

    private Map<Integer, FeeService> map;

    public ServiceFeeFactory() {

        // 该工厂管理所有的策略接口实现类
        List<FeeService> feeServices = new ArrayList<>();

        feeServices.add(new OrdinaryLevel());
        //feeServices.add(new JuniorLevel());
        // 把所有策略实现的集合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 工具类

​ 新建通过一个工具类管理计费规则的调用,并对不符合规则的公司级别输入抛IllegalArgumentException

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

核心是通过Map的get()方法,根据传入 type,即可获取到对应会员类型计费规则的实现,从而减少了if-else的业务判断。

3.5 测试

import org.junit.Test;

public class DemoTest {

    @Test
    public void test() {
        Double fees = upMethod(0,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);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TQZDSRet-1583917880354)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200311161333.png)]

四、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 不同计费规则的实现

把策略的实现类交给Spring容器管理

@Component
public class OrdinaryLevel implements FeeService {


    @Override
    public Double compute(Double amount) {
        // 具体的实现根据业务需求修改
        return 9.99;
    }

    @Override
    public Integer getType() {
        return LevelEnum.ORDINARY_LEVEL.getCode();
    }
}

4.3 别名转换

程序如何通过一个表示、怎么识别解析这个标识,找到对应的策略实现类

  • 可以在配置文件中指定,便于维护

application.yml

alias:
  aliasMap:
    first: OrdinaryLevel
    second: juniorLevel
    third: intermediateLevel
    fourth: seniorLevel

AliasEntity.java

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.HashMap;

@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 策略工厂

import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Map;

@Component
public 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获取其实现类
  • 找不到对应的策略的实现类,抛出异常

测试

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

五、总结

主要使用了设计模式中的策略模式,因为符合使用条件:

  • 系统中有很多类,而他们的区别仅仅在于他们的行为不同
  • 一个系统需要动态地在几种算法中选择一种

5.1 策略模式角色

风尘博客

  • Context:环境类
    • 上下文角色,起到承上启下的作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化,对应本文的ServiceFeeFactory.java
  • Strategy:抽象策略类
    • 定义算法的接口,对应本文的FeeService.java
  • ConcreteStrategy:具体策略类
    ion
    System.out.println(serviceFeeHolder.getType(“zero”));
    }
    }

### 五、总结

主要使用了设计模式中的策略模式,因为符合使用条件:

* 系统中有很多类,而他们的区别仅仅在于他们的行为不同
* 一个系统需要动态地在几种算法中选择一种

#### 5.1 策略模式角色

[外链图片转存中...(img-dTOBMjmk-1583917880356)]

* Context:环境类
  * 上下文角色,起到承上启下的作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化,对应本文的ServiceFeeFactory.java
* Strategy:抽象策略类
  * 定义算法的接口,对应本文的FeeService.java
* ConcreteStrategy:具体策略类
  * 实现具体策略的接口,对应:OrdinaryLevel
发布了64 篇原创文章 · 获赞 76 · 访问量 9861

猜你喜欢

转载自blog.csdn.net/issunmingzhi/article/details/104800737