首页 > 其他分享 >代码精简之路-责任链模式

代码精简之路-责任链模式

时间:2024-11-28 12:10:39浏览次数:7  
标签:return service 角色 代码 之路 VIP 精简 普通用户 public

前言

常说c#、java是面向对象的语言,但我们平时都是在用面向过程的思维写代码,实现业务逻辑像记流水账一样,大篇if else的判断;对业务没有抽象提炼、代码没有分层。随着需求变化、功能逐步拓展、业务逻辑逐渐复杂;代码越来越长、if else嵌套越来越多,代码会变成程序员都厌恶的"屎山"。这种代码后期维护成本非常高、牵一发而动全身、改一处逻辑战战兢兢。假如我们完成开发任务交差,后期维护不关自己的事;但是长期做重复的CRUD、记流水账对我们没有好处。虽然项目不是自己的,但是时间是自己的,这样几年过去似乎没有精进变化,长期下去年龄增大会逐渐丧失竞争力。

下面记录今天开发的一个小功能,演示一步一步重构的过程。

需求

  1. 有一个智能识别的api给用户调用。角色有两个:管理员、普通用户。管理员不限次数调用,普通用户每日限用五次。

简单实现,只判断如果是普通用户就检查次数,不满足就返回提示:

if (service.isNormalUser() && service.freeNumUseUp()) {
    return AjaxResult.error("普通用户免费识别次数已使用完!");
}

// todo:调用识别接口
  1. 功能演变:普通用户可充值后升级为VIP用户,VIP用户在有效期内不限次数使用,过期以后降为普通用户。
    增加VIP角色的检查后:
if (service.isVipUser() && service.vipUserExpire()) {
    return AjaxResult.error("会员已到期!");
}
if (service.isNormalUser() && service.freeNumUseUp()) {
    return AjaxResult.error("普通用户免费识别次数已使用完!");
}

// todo:调用识别接口

以上修改的问题:普通用户充值以后,是增加一个VIP的角色而不是把原普通用户角色更新为VIP角色。此时这个用户有两个角色,那么上面的代码先判断VIP角色是否到期是没问题的,但是下面又判断了是否为普通用户就有问题了,因为他有两个角色呀,VIP未到期时第2个条件也满足了会给出不合理的提示。怎么改,首先想到的是不是检查VIP后就不检查普通用户了?于是修改为:

if (service.isVipUser()) {
    if (service.vipUserExpire()) {
        return AjaxResult.error("会员已到期!");
    }
} else {
    if (service.isNormalUser() && service.freeNumUseUp()) {
        return AjaxResult.error("普通用户免费识别次数已使用完!");
    }
}

// todo:调用识别接口

以上仍然有问题,如果是VIP角色就不会检查普通用户角色了,可是按需求VIP到期以后他还具有普通用户角色,可以在每天免费次数内使用。于是再改:

boolean dontPass = service.isVipUser() && service.vipUserExpire();
if (dontPass) {
    dontPass = service.isNormalUser() && service.freeNumUseUp();
    if (dontPass) {
        return AjaxResult.error("普通用户免费识别次数已使用完!");
    } else {
        return AjaxResult.error("会员已到期!");
    }
}

// todo:调用识别接口

以上修改可以满足VIP和普通用户的检查了,还差了管理员的判断,还要再嵌套:

boolean dontPass = !service.isAdmin();
if (dontPass) {
    dontPass = service.isVipUser() && service.vipUserExpire();
    if (dontPass) {
        dontPass = service.isNormalUser() && service.freeNumUseUp();
        if (dontPass) {
            return AjaxResult.error("普通用户免费识别次数已使用完!");
        } else {
            return AjaxResult.error("会员已到期!");
        }
    }
}

// todo:调用识别接口

终于满足3个角色的检查了,加了3层if判断。以后再出现新的角色怎么办?如果功能交给同事来升级,原来的代码轻易不敢动只能再嵌套。

梳理以上需求,3个角色有任意一个通过就可以了。实际上检查时可以按以下先后顺序逐个过,最后一个不满足才返回提示。

a. 是否有管理员角色,否进入下一级
b. 是否有VIP角色且未到期,否进入下一级
c. 是否有普通用户角色且满足免费次数条件,否进入下一级;如果没有下一级则检查不通过。

重新设计

  1. 审批角色接口,主要两个功能:a. 角色判断(当前用户是否为本角色),b. 是否检查(审批)通过
public interface IAudit {
    /**
     * 角色判断:是否为我的责任
     *
     * @return
     */
    boolean isMyDuty();
    
    /**
     * 是否通过
     *
     * @return
     */
    boolean auditPass();
    
    /**
     * 检查(审批)意见:不通过时返回空字符串
     *
     * @return
     */
    String auditMessage();
}
  1. 审批角色抽象类,实现审批角色接口,并且是3个角色实现类的父类,充当审批角色接口和角色实现类的中间过度。作用是判断检查(审批)是否通过,这里不大容易理解,实际3个角色的实现类分别实现接口就可以了,没有这个中间过度也可以的。为什么要加这个中间类?因为最终检查是否通过要调用isMyDuty和auditPass两个方法,这里可以把这两个方法的调用合并为一个方法,其实就是把判断角色和角色的检查条件统一在这个类而不是在3个实现类里去分别写了,为什么?因为3个实现类要写的判断都是完全一样的代码isMyDuty()&&auditPass(),作用就是本来要写3行,现在只写1行。看上去没有必要?因为现在只有3个类呀,如果以后扩展到5个角色,5类那多了。还有,如果是功能修改呢,那就要6个类里分别改了。每改一个类都需要针对这个类单独测试。修改测试花时间多了,这里只有一次修改测试。
public abstract class AbstractAudit implements IAudit {
    /**
     * 角色是否检查通过
     *
     * @return
     */
    public boolean checkPass() {
        return isMyDuty() && auditPass();
    }
}
  1. 3个角色的实现类。
  • 管理员:
@Service
public class AdminAudit extends AbstractAudit {
    @Autowired
    private IdentifyService identifyService;
    
    @Override
    public boolean isMyDuty() {
        return identifyService.isAdmin();
    }
    
    @Override
    public boolean auditPass() {
        return true;
    }
    
    /**
     * 管理员是没有限制的,所以没有提示
     *
     * @return
     */
    @Override
    public String auditMessage() {
        return "";
    }
}
  • VIP用户:
@Service
public class VipUserAudit extends AbstractAudit {
    @Autowired
    private IdentifyService identifyService;
    
    @Override
    public boolean isMyDuty() {
        return identifyService.isVipUser();
    }
    
    @Override
    public boolean auditPass() {
        return !identifyService.vipUserExpire();
    }
    
    /**
     * 这里还需要优化,因为isMyDuty和auditPass可能被调用两次,可以将isMyDuty、auditPass返回值存在临时变量中
     *
     * @return
     */
    @Override
    public String auditMessage() {
        if (!isMyDuty()) {
            return "不是会员";
        } else if (!auditPass()) {
            return "会员过期";
        }
        return "";
    }
}
  • 普通用户:
@Service
public class NormalUserAudit extends AbstractAudit {
    @Autowired
    private IdentifyService identifyService;
    
    @Override
    public boolean isMyDuty() {
        return identifyService.isNormalUser();
    }
    
    @Override
    public boolean auditPass() {
        return !identifyService.freeNumUseUp();
    }
    
    @Override
    public String auditMessage() {
        return "普通用户免费识别次数已使用完";
    }
}
  1. 审批责任链类。作用为添加审批人、审批返回结果。
public class AuditChain {

    private List<AbstractAudit> chain = new ArrayList<>();

    /**
     * 添加审批人
     *
     * @param auditor
     */
    public void add(AbstractAudit auditor) {
        chain.add(auditor);
    }


    /**
     * 检查/审批
     *
     * @return
     */
    public Result audit() {
        Result result = new Result();
        // 是否检查通过
        boolean pass = chain.stream().anyMatch(a -> a.checkPass());
        result.setPass(pass);
        if (!pass) {
            String msg = chain.stream().map(c -> c.auditMessage()).filter(m -> Strings.isNotBlank(m)).collect(Collectors.joining(","));
            result.setMsg(msg);
        }
        return result;
    }


    @Data
    public class Result {
        private boolean pass;
        private String msg;
    }
}
  1. 实现检查
// 审批责任链中加入3个角色,这里用的Spring Boot开发,3个角色都是容器注入的,其它框架中手动创建实例

// 添加审批人角色
auditChain.add(adminAudit);
auditChain.add(vipUserAudit);
auditChain.add(normalUserAudit);

// 审批结果
AuditChain.Result auditResult = auditChain.audit();
if (!auditResult.isPass()) {
    return AjaxResult.error(auditResult.getMsg());
}

总结

最终的实现代码简洁明了,易维护、易扩展升级:

  1. 核心方法只有auditChain.add和auditChain.audit,一眼看去就能明白作用是加入审批人和实现审批。
  2. 如何扩展功能加入其它角色?创建新的角色类并继承AbstractAudit,并加入到责任链中。不需要在原来的if中嵌套了。
  3. 现在的检查是多个角色中有任意一个通过即可,转换到审批场景就是多角色审批,其中一个角色审批通过即可。如果要需求改成多个角色全部审批通过才行呢?其实就是责任人链中or的关系改为and关系。 只需要修改AuditChain类的audit方法,将chain.stream().anyMatch改为chain.stream().allMatch。anyMatch表示任意一个匹配,allMatch表示全部匹配。如果要在改造前的代码中要实现or到and的变化,原有代码几乎要完全重写。

学习交流:

标签:return,service,角色,代码,之路,VIP,精简,普通用户,public
From: https://www.cnblogs.com/cy2011/p/18572619

相关文章

  • 代码编写之道:十条经验引领高效编程之旅
    在编程的世界里摸爬滚打多年,我积累了不少宝贵的经验,在此总结出10条与各位开发者分享。本文大纲一、复用要理性有些时候并不需要过度追求高可复用性。尤其是在存在大量自定义行为,需要传入多个参数来达成目的的情况下,强行追求复用可能会造成后续维护的不便。比如在一些特定业......
  • 【热门主题】000067 React前端框架:探索高效Web开发之路
    前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦......
  • 《 C++ 点滴漫谈: 三 》穿越代码的迷雾:C++ 关键字的应用与未来
    摘要这篇博客深入探讨了C++语言中的所有关键字,涵盖了它们的作用、使用场景及其在编程中的重要性。从基础的控制流关键字到现代C++引入的关键字扩展,每个关键字都进行了详细解析。博客还展示了C++关键字的实际应用,帮助读者理解如何有效地运用它们来编写高效、清晰的代......
  • 代码随想录 -- 单调栈 -- 每日温度
    单调栈适用场景一维数组中,求任意元素左边(右边)第一个比他大(小)的元素的位置。使用时明确单调栈中存放的是数组下标单调栈是递增还是递减每日温度739.每日温度-力扣(LeetCode)思路:题目中要求当前元素的右边比当前元素大的第一个元素的位置,所以单调栈是递增的,单调栈中存......
  • 代码随想录 -- 动态规划 -- 最长回文子序列
    516.最长回文子序列-力扣(LeetCode)思路:dp数组的含义:dp[i][j]:字符串s从i到j的最长回文子序列的长度为dp[i][j]递推公式:当s[i]=s[j]时:dp[i][j]=dp[i+1][j-1]+2当s[i]!=s[j]时:dp[i][j]=max(dp[i][j-1],dp[i+1][j])初始化:当i=j时:dp[i][j]=1遍历顺序:从下到上,从左到右最......
  • 代码随想录 -- 动态规划 -- 编辑距离
    72.编辑距离-力扣(LeetCode)思路:dp数组的含义:dp[i][j]:以i-1为结尾的word1和以j-1为结尾的word2的最少操作数为dp[i][j]递推公式:当word1[i-1]=word2[j-1]时:无需进行任何操作故dp[i][j]=dp[i-1][j-1]当word1[i-1]!=word2[j-1]时:删除一个字符:dp[i][j]=max(dp[i-1][j......
  • 你有用记事本来写过代码吗?说说你的感受!
    优点:轻便快捷:打开速度快,不需要安装和配置复杂的IDE,随时随地可以写代码。对于简单的HTML、CSS和JavaScript修改来说非常方便。专注代码:没有额外的功能干扰,可以更加专注于代码本身。学习基础:对于初学者来说,使用记事本可以更好地理解代码的结构和原理,避免对IDE的依赖。......
  • 帝国CMS内容页模板点击改变字体大小的js代码
    加入JS代码:<scripttype="text/javascript">functionFontZoom(fsize){varctext=document.getElementById("news");ctext.style.fontSize=fsize+"px";}</script>定制框架:<divid="news"&......
  • 小白的C++之路(一)
    作为编程小白,最近开始学习C++了。为学习C++,装了一个VScode,但是写的第一个代码就出现了问题,也是让本小白几天时间笑不出来。前前后后搜了不少文章,也搜了好些视频,总是没有解决成功,今天晚上糊里糊涂突然就成功了,特此记录一下。问题如下: 解决经历:为了解决这两个问题,真是搜了......
  • 编程之路,从0开始:预处理详解(完结篇)
            Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路!我的博客:<但凡.我的专栏:编程之路        这一篇预处理详解是我们C语言基础内容学习的最后一篇,也是我们的专栏:编程之路的最后一篇!从今日起,我将不定期更新新的内容,开始新的章节......