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

news/2024/11/15 8:20:31 标签: java, 设计模式

一、场景介绍

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

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

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

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

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

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

朝廷规定,

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

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

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

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

二、思路分析

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

java">    public void computerTax() {
        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、表结构设计

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、对象设计

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:

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


http://www.niftyadmin.cn/n/5752951.html

相关文章

[Electron]总结:如何创建Electron+Element Plus的项目

我将结合官网手册与AI问到的信息&#xff0c;直接给出步骤&#xff0c;与命令。 一、准备环境 首先在C盘Users&#xff0c;你的登录的账号名文件夹下&#xff0c;编辑.npmrc文件。添加镜像地址。 如果使用了yarn&#xff0c;则是.yarnrc。可以全部都配置。 npm install -g …

深度学习之pytorch常见的学习率绘制

文章目录 0. Scope1. StepLR2. MultiStepLR3. ExponentialLR4. CosineAnnealingLR5. ReduceLROnPlateau6. CyclicLR7. OneCycleLR小结参考文献 https://blog.csdn.net/coldasice342/article/details/143435848 0. Scope 在深度学习中&#xff0c;学习率&#xff08;Learning R…

策略模式、状态机详细解读

策略模式 (Strategy Pattern) 策略模式 (Strategy Pattern) 是一种行为型设计模式&#xff0c;旨在将一组算法封装成独立的类&#xff0c;使得它们可以相互替换。这种模式让算法的变化不会影响到使用算法的客户&#xff0c;减少了类之间的耦合。策略模式通常用于处理一类问题&…

Redis缓存雪崩、击穿、穿透技术解析及解决方案

在使用 Redis 缓存时&#xff0c;经常会遇到一些异常问题。 概括来说有 4 个方面&#xff1a; 缓存中的数据和数据库中的不一致&#xff1b;缓存雪崩&#xff1b;缓存击穿&#xff1b;缓存穿透。 关于第一个问题【缓存中的数据和数据库中的不一致】&#xff0c;在之前的文章…

算法训练(leetcode)二刷第二十六天 | *452. 用最少数量的箭引爆气球、435. 无重叠区间、*763. 划分字母区间

刷题记录 *452. 用最少数量的箭引爆气球435. 无重叠区间*763. 划分字母区间笨拙版进阶版 *452. 用最少数量的箭引爆气球 leetcode题目地址 先对气球的坐标按照Xstart进行升序排序&#xff0c;只要两个气球之间挨着就可以一箭射穿&#xff0c;因此排序后查看后一个气球的起始坐…

MATLAB用CNN-LSTM神经网络的语音情感分类深度学习研究

全文链接&#xff1a;https://tecdat.cn/?p38258 在语音处理领域&#xff0c;对语音情感的分类是一个重要的研究方向。本文将介绍如何通过结合二维卷积神经网络&#xff08;2 - D CNN&#xff09;和长短期记忆网络&#xff08;LSTM&#xff09;构建一个用于语音分类任务的网络…

使用 Python 脚本在 Ansys Mechanical 中自动生成命名选择

介绍 Ansys Mechanical 中的命名选择是对几何图形或 FEM 实体进行分组以备将来在模型定义中使用&#xff0c;即网格控制、材料分配、接触定义等或后处理的便捷方法。在上一篇文章中&#xff0c;我们讨论了使用几何选择或通过工作表手动创建命名选择。在这里&#xff0c;我们将…

Java 实现鼠标单击右键弹出菜单项

在界面的任意地方鼠标右键点击出现一个菜单项 "Intro"&#xff0c;并且点击该项后弹出一个新窗口。 public class frame extends JFrame {public frame() {setTitle("title");setSize(500, 500);// JPopupMenu 用于创建和显示弹出菜单JPopupMenu popupMe…