Java 责任链模式 减少 if else 实战案例

一、场景介绍

假设有这么一个朝廷,它有 县-->府-->省-->朝廷,四级行政机构。

这四级行政机构的关系如下表:

1、县-->府-->省-->朝廷:有些地方有完整的四级行政机构。

2、县-->府-->朝廷:直隶府,朝廷直隶。

3、县-->省-->朝廷:有些地方可以没有府级行政机构。

4、县-->朝廷:直隶县,朝廷直隶。

朝廷规定,

县地方收上来的赋税,县衙可以留存10%,府署可以留存20%,省署可以留存30%。

但倘若下一级行政机构缺失,它的比例累加到上一级。

最后可能的赋税分配比例如下:

现在有一个县,收上来10万两白银,请设计一种模型,来计算各个层级行政机构所能分配到的赋税金额。

二、思路分析

如果按照正常的程序设计思路,伪代码可能如下:

public void computeTax() {
    if (县的上级是朝廷) {
        计算县的赋税
        计算朝廷的赋税
    } else if(县的上级是府) {
        计算县的赋税
        获取县的上级府
        if (府的上级是朝廷) {
            计算府的赋税 
            计算朝廷的赋税
        } else if(府的上级是省) {
            获取府的上级省
            计算省的赋税
            计算朝廷的赋税
        }
    } else if(县的上级是省) {
        获取县的上级省
        计算省的赋税
        计算朝廷的赋税
    }
}

分支语句特别多,条件判断又臭又长,可读性跟可维护性都很差,这还只是四级,如果行政区划层级再多一点,那么这个方法可能会有上千行,堆屎山一样。

下面,我们用责任链来改造这个方法。

三、代码实现

1、枚举设计

设计一个枚举,用来表示行政等级

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 行政等级枚举
 */
@AllArgsConstructor
@Getter
public enum GovGradeEnum {

    IMPERIAL_COURT(0, "朝廷"),
    PROVINCE(1, "省"),
    RESIDENCE(2, "府"),
    COUNTY(3, "县");

    private final Integer code;
    private final String name;
}

2、表结构设计

create table gov_division
(
    id        int auto_increment comment '主键'
        primary key,
    name      varchar(20) not null comment '区划名称',
    grade     int         not null comment '区划所处等级',
    parent_id int         not null comment '区划上一级ID'
)
    comment '行政区划表';
create table tax
(
    id          int auto_increment comment '主键'
        primary key,
    grade       int            not null comment '区划等级',
    division_id int            not null comment '区划ID',
    rate        decimal(10, 2) not null comment '赋税比例',
    amount      decimal(10, 2) not null comment '赋税金额'
) comment '赋税分配表';

3、对象设计

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;

import lombok.Data;

@Data
@TableName("gov_division")
public class GovDivision implements Serializable {

    private static final long serialVersionUID = 1L;

    /** 主键 */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /** 区划名称 */
    private String name;

    /** 区划所处等级 */
    private Integer grade;

    /** 区划上一级ID */
    private Integer parentId;
}
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.*;

/**
 * 行政区划表 Mapper 接口
 */
@Mapper
public interface GovDivisionMapper extends BaseMapper<GovDivision> {
}

给对象添加 @Builder 注解是为了方便后面用构造器方式构造对象。

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.math.BigDecimal;

import lombok.Builder;
import lombok.Data;

@Data
@TableName("tax")
@Builder
public class Tax implements Serializable {

    private static final long serialVersionUID = 1L;

    /** 主键 */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /** 区划等级 */
    private Integer grade;

    /** 区划ID */
    private Integer divisionId;

    /** 赋税比例 */
    private BigDecimal rate;

    /** 赋税金额 */
    private BigDecimal amount;
}
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.*;

import java.util.List;

/**
 *  赋税 Mapper 接口
 */
@Mapper
public interface TaxMapper extends BaseMapper<Tax> {
    void batchInsert(List<Tax> list);
}

 TaxMapper.xml:

<insert id="batchInsert" parameterType="java.util.List">
        INSERT INTO tax (
          grade, division_id, rate, amount
        ) VALUES
        <foreach collection="list" item="item" index="index" separator=",">
            (
            #{item.grade}, #{item.divisionId}, #{item.rate}, #{item.amount}
            )
        </foreach>
</insert>

 4、责任链设计

4.1 责任处理接口

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

/** 责任处理接口 */
public interface TaxHandler {

    /**
     * 责任链处理接口
     * @param param 入参
     * @param config 赋税比例配置
     * @param sum 累计比例
     * @param use 使用了多少比例
     * @param taxList 生成的赋税列表
     */
    void handle(ReqParam param, Map<Integer, BigDecimal> config, BigDecimal sum, BigDecimal use, List<Tax> taxList);

    /** 构造赋税默认方法 */
    default Tax buildTax(Integer divisionId, Integer grade, BigDecimal rate, BigDecimal amount) {
        return
                Tax.builder().divisionId(divisionId)
                        .grade(grade)
                        .rate(rate)
                        .amount(amount)
                        .build();
    }
}
import lombok.Data;
import java.math.BigDecimal;

@Data
public class ReqParam {
    /** 区划ID */
    private Integer id;
    /** 区划等级 */
    private Integer grade;
    /** 上级区划ID */
    private Integer parentId;
    /** 今年赋税总额 */
    private BigDecimal totalTax;
}

 4.2 责任处理实现类

我们实现 县、府、省、朝廷 四个实现类,让他们组成

县-->府-->省-->朝廷  这样一条责任链,并在请求参数中带上 grade,让每一个层级只处理自己 grade 的请求。

import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

/** 县处理者 */
@Service
@AllArgsConstructor
public class CountryTaxHandler implements TaxHandler {
    /** 引用府处理者 */
    private final ResidenceTaxHandler residenceTaxHandler;

    private final GovDivisionMapper govDivisionMapper;

    @Override
    public void handle(ReqParam param, Map<Integer, BigDecimal> config,
                       BigDecimal sum, BigDecimal use, List<Tax> taxList) {
        sum = sum.add(config.get(GovGradeEnum.COUNTY.getCode()));
        if (GovGradeEnum.COUNTY.getCode().equals(param.getGrade())) {
            taxList.add(buildTax(param.getId(), param.getGrade(), sum,
                    param.getTotalTax().multiply(sum)));
            use = use.add(sum);
            sum = BigDecimal.ZERO;
            // 获取上级
            GovDivision govDivision = govDivisionMapper.selectById(param.getParentId());
            BeanUtils.copyProperties(govDivision, param);
            // 责任链向下传递
            residenceTaxHandler.handle(param, config, sum, use, taxList);
        } else {
            // 责任链向下传递
            residenceTaxHandler.handle(param, config, sum, use, taxList);
        }
    }
}
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

/** 府处理者 */
@Service
@AllArgsConstructor
public class ResidenceTaxHandler implements TaxHandler {
    /** 引用省处理者 */
    private final ProvinceTaxHandler provinceTaxHandler;

    private final GovDivisionMapper govDivisionMapper;

    @Override
    public void handle(ReqParam param, Map<Integer, BigDecimal> config,
                       BigDecimal sum, BigDecimal use, List<Tax> taxList) {
        sum = sum.add(config.get(GovGradeEnum.RESIDENCE.getCode()));
        if (GovGradeEnum.RESIDENCE.getCode().equals(param.getGrade())) {
            taxList.add(buildTax(param.getId(), param.getGrade(), sum,
                    param.getTotalTax().multiply(sum)));
            use = use.add(sum);
            sum = BigDecimal.ZERO;
            // 获取上级
            GovDivision govDivision = govDivisionMapper.selectById(param.getParentId());
            BeanUtils.copyProperties(govDivision, param);
            provinceTaxHandler.handle(param, config, sum, use, taxList);
        } else {
            provinceTaxHandler.handle(param, config, sum, use, taxList);
        }
    }
}
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

/** 省处理者 */
@Service
@AllArgsConstructor
public class ProvinceTaxHandler implements TaxHandler {
    /** 引用朝廷 */
    private ImperialCourtTaxHandler imperialCourtTaxHandler;

    private final GovDivisionMapper govDivisionMapper;

    @Override
    public void handle(ReqParam param, Map<Integer, BigDecimal> config,
                       BigDecimal sum, BigDecimal use, List<Tax> taxList) {
        sum = sum.add(config.get(GovGradeEnum.PROVINCE.getCode()));
        if (GovGradeEnum.PROVINCE.getCode().equals(param.getGrade())) {
            taxList.add(buildTax(param.getId(), param.getGrade(), sum,
                    param.getTotalTax().multiply(sum)));
            use = use.add(sum);
            sum = BigDecimal.ZERO;
            // 获取上级
            GovDivision govDivision = govDivisionMapper.selectById(param.getParentId());
            BeanUtils.copyProperties(govDivision, param);
            imperialCourtTaxHandler.handle(param, config, sum, use, taxList);
        } else {
            imperialCourtTaxHandler.handle(param, config, sum, use, taxList);
        }
    }
}
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

/** 朝廷 */
@Service
@AllArgsConstructor
public class ImperialCourtTaxHandler implements TaxHandler {

    @Override
    public void handle(ReqParam param, Map<Integer, BigDecimal> config,
                       BigDecimal sum, BigDecimal use, List<Tax> taxList) {
        BigDecimal rate = BigDecimal.ONE.subtract(use);
        if (GovGradeEnum.IMPERIAL_COURT.getCode().equals(param.getGrade())) {
            taxList.add(buildTax(param.getId(), param.getGrade(), rate,
                    param.getTotalTax().multiply(rate)));
        }
        // 责任链结束
    }
}

五、测试用例

1、直隶县1今年交了10万两白银:

import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = AlMallApplication.class)
@Slf4j
public class TaxTest {
    /** 税收留存比例配置类,这里为了简单自己写死,也可以来自数据库 */
    private Map<Integer, BigDecimal> config;

    @Autowired
    private TaxMapper taxMapper;

    @Autowired
    private CountryTaxHandler countryTaxHandler;

    @Autowired
    private GovDivisionMapper govDivisionMapper;

    @Before
    public void init() {
        config = new HashMap<>();
        config.put(GovGradeEnum.COUNTY.getCode(), BigDecimal.valueOf(0.1));
        config.put(GovGradeEnum.RESIDENCE.getCode(), BigDecimal.valueOf(0.2));
        config.put(GovGradeEnum.PROVINCE.getCode(), BigDecimal.valueOf(0.3));
    }

    @Test
    public void test1() {
        // 直隶县1-->朝廷
        GovDivision country = govDivisionMapper.selectById(2);
        ReqParam param = new ReqParam();
        // 直隶县1 今年交了10万两白银
        param.setTotalTax(BigDecimal.valueOf(10));
        BeanUtils.copyProperties(country, param);
        List<Tax> list = new ArrayList<>();
        countryTaxHandler.handle(param, config, BigDecimal.ZERO, BigDecimal.ZERO, list);
        taxMapper.batchInsert(list);
    }
}

2、无府县1今年交了10万两白银

import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = AlMallApplication.class)
@Slf4j
public class TaxTest {
    /** 税收留存比例配置类,这里为了简单自己写死,也可以来自数据库 */
    private Map<Integer, BigDecimal> config;

    @Autowired
    private TaxMapper taxMapper;

    @Autowired
    private CountryTaxHandler countryTaxHandler;

    @Autowired
    private GovDivisionMapper govDivisionMapper;

    @Before
    public void init() {
        config = new HashMap<>();
        config.put(GovGradeEnum.COUNTY.getCode(), BigDecimal.valueOf(0.1));
        config.put(GovGradeEnum.RESIDENCE.getCode(), BigDecimal.valueOf(0.2));
        config.put(GovGradeEnum.PROVINCE.getCode(), BigDecimal.valueOf(0.3));
    }

    @Test
    public void test2() {
        // 无府县1-->无府省1-->朝廷
        GovDivision country = govDivisionMapper.selectById(4);
        ReqParam param = new ReqParam();
        // 无府县1 今年交了10万两白银
        param.setTotalTax(BigDecimal.valueOf(10));
        BeanUtils.copyProperties(country, param);
        List<Tax> list = new ArrayList<>();
        countryTaxHandler.handle(param, config, BigDecimal.ZERO, BigDecimal.ZERO, list);
        taxMapper.batchInsert(list);
    }
}

3、无省县1今年交了10万两白银

import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = AlMallApplication.class)
@Slf4j
public class TaxTest {
    /** 税收留存比例配置类,这里为了简单自己写死,也可以来自数据库 */
    private Map<Integer, BigDecimal> config;

    @Autowired
    private TaxMapper taxMapper;

    @Autowired
    private CountryTaxHandler countryTaxHandler;

    @Autowired
    private GovDivisionMapper govDivisionMapper;

    @Before
    public void init() {
        config = new HashMap<>();
        config.put(GovGradeEnum.COUNTY.getCode(), BigDecimal.valueOf(0.1));
        config.put(GovGradeEnum.RESIDENCE.getCode(), BigDecimal.valueOf(0.2));
        config.put(GovGradeEnum.PROVINCE.getCode(), BigDecimal.valueOf(0.3));
    }

    @Test
    public void test3() {
        // 无省县1-->无省府1-->朝廷
        GovDivision country = govDivisionMapper.selectById(6);
        ReqParam param = new ReqParam();
        // 无省县1 今年交了10万两白银
        param.setTotalTax(BigDecimal.valueOf(10));
        BeanUtils.copyProperties(country, param);
        List<Tax> list = new ArrayList<>();
        countryTaxHandler.handle(param, config, BigDecimal.ZERO, BigDecimal.ZERO, list);
        taxMapper.batchInsert(list);
    }
}

4、正常县1今年交了10万两白银

import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = AlMallApplication.class)
@Slf4j
public class TaxTest {
    /** 税收留存比例配置类,这里为了简单自己写死,也可以来自数据库 */
    private Map<Integer, BigDecimal> config;

    @Autowired
    private TaxMapper taxMapper;

    @Autowired
    private CountryTaxHandler countryTaxHandler;

    @Autowired
    private GovDivisionMapper govDivisionMapper;

    @Before
    public void init() {
        config = new HashMap<>();
        config.put(GovGradeEnum.COUNTY.getCode(), BigDecimal.valueOf(0.1));
        config.put(GovGradeEnum.RESIDENCE.getCode(), BigDecimal.valueOf(0.2));
        config.put(GovGradeEnum.PROVINCE.getCode(), BigDecimal.valueOf(0.3));
    }

    @Test
    public void test4() {
        // 正常县1-->正常府1-->正常省1-->朝廷
        GovDivision country = govDivisionMapper.selectById(9);
        ReqParam param = new ReqParam();
        // 正常县1 今年交了10万两白银
        param.setTotalTax(BigDecimal.valueOf(10));
        BeanUtils.copyProperties(country, param);
        List<Tax> list = new ArrayList<>();
        countryTaxHandler.handle(param, config, BigDecimal.ZERO, BigDecimal.ZERO, list);
        taxMapper.batchInsert(list);
    }

}

六、总结

先说缺点:

责任链的缺点,是造成类的膨胀。

大家仔细观察上面的代码,会发现,责任处理类,好像是把刚开始的 if else 伪代码,分到一个个处理类里面去了而已。

而且使用设计模式,它甚至并没有提高代码的执行效率。

 优点:

写代码并不是做外包,写完就扔,它还得考虑可读性、可维护性和可扩展性。

可维护性和可扩展性的前提,就是可读性。

一段代码如果过几天,连自己都看不明白,那这种代码,它基本就没有什么可维护性了。

if else 不是不能写,而是如果分支太多,自己调试的时候,都不知道要走过多少 if else 才能到达自己的断点,那这样的代码,你怎么修改?改完你又怎么回归测试?

责任链的优点,恰恰就是它的可读性、可扩展性非常好。

每个处理类只处理自己职责范围内的消息,对于其他消息一律往下传。把变化封装在每一个类里面。对于上面的例子,如果现在有新的层级,我们只需要加一个枚举类型 grade,在加一个处理类,并把处理类加入到责任链里面,这样的可扩展性大大提高。

上一篇:Linux_1


下一篇:XP系统下用mod_jk 1.2.40整合apache2.2.16和tomcat 6.0.29,让apache可以同时访问php和jsp页面