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

一、场景介绍

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

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

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

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

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

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

朝廷规定,

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

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

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

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

二、思路分析

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

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

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

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

三、代码实现

1、枚举设计

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

java 复制代码
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、表结构设计

sql 复制代码
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 '行政区划表';
sql 复制代码
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、对象设计

java 复制代码
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;
}
java 复制代码
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.*;

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

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

java 复制代码
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;
}
java 复制代码
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:

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 责任处理接口

java 复制代码
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();
    }
}
java 复制代码
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 的请求。

java 复制代码
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);
        }
    }
}
java 复制代码
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);
        }
    }
}
java 复制代码
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);
        }
    }
}
java 复制代码
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万两白银:

java 复制代码
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万两白银

java 复制代码
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万两白银

java 复制代码
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万两白银

java 复制代码
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,在加一个处理类,并把处理类加入到责任链里面,这样的可扩展性大大提高。

相关推荐
安之若素^4 分钟前
启用不安全的HTTP方法
java·开发语言
ruanjiananquan9910 分钟前
c,c++语言的栈内存、堆内存及任意读写内存
java·c语言·c++
chuanauc37 分钟前
Kubernets K8s 学习
java·学习·kubernetes
一头生产的驴1 小时前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao1 小时前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc7871 小时前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
YuTaoShao3 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展
程序员张33 小时前
SpringBoot计时一次请求耗时
java·spring boot·后端
llwszx6 小时前
深入理解Java锁原理(一):偏向锁的设计原理与性能优化
java·spring··偏向锁
Small black human7 小时前
设计模式-应用分层
设计模式