Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >干掉 “重复代码” 的技巧有哪些

干掉 “重复代码” 的技巧有哪些

作者头像
每周聚焦
发布于 2023-01-03 08:27:09
发布于 2023-01-03 08:27:09
45500
代码可运行
举报
文章被收录于专栏:每周聚焦每周聚焦
运行总次数:0
代码可运行

软件工程师和码农最大的区别就是平时写代码时习惯问题,码农很喜欢写重复代码而软件工程师会利用各种技巧去干掉重复的冗余代码。

业务同学抱怨业务开发没有技术含量,用不到设计模式Java 高级特性OOP,平时写代码都在堆 CRUD,个人成长无从谈起。

其实,我认为不是这样的。设计模式、OOP 是前辈们在大型项目中积累下来的经验,通过这些方法论来改善大型项目的可维护性。反射、注解、泛型等高级特性在框架中大量使用的原因是,框架往往需要以同一套算法来应对不同的数据结构,而这些特性可以帮助减少重复代码,提升项目可维护性。

在我看来,可维护性是大型项目成熟度的一个重要指标,而提升可维护性非常重要的一个手段就是减少代码重复。那为什么这样说呢?

  • 如果多处重复代码实现完全相同的功能,很容易修改一处忘记修改另一处,造成 Bug
  • 有一些代码并不是完全重复,而是相似度很高,修改这些类似的代码容易改(复制粘贴)错,把原本有区别的地方改为了一样。

今天,我就从业务代码中最常见的三个需求展开,聊聊如何使用 Java 中的一些高级特性、设计模式,以及一些工具消除重复代码,才能既优雅又高端。通过今天的学习,也希望改变你对业务代码没有技术含量的看法。

1. 利用工厂模式 + 模板方法模式,消除 if…else 和重复代码

假设要开发一个购物车下单的功能,针对不同用户进行不同处理:

  • 普通用户需要收取运费,运费是商品价格的 10%,无商品折扣;
  • VIP 用户同样需要收取商品价格 10% 的快递费,但购买两件以上相同商品时,第三件开始享受一定折扣;
  • 内部用户可以免运费,无商品折扣。

我们的目标是实现三种类型的购物车业务逻辑,把入参 Map 对象(Key 是商品 ID,Value 是商品数量),转换为出参购物车类型 Cart。

先实现针对普通用户的购物车处理逻辑:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//购物车
@Data
public class Cart {
    //商品清单
    private List<Item> items = new ArrayList<>();
    //总优惠
    private BigDecimal totalDiscount;
    //商品总价
    private BigDecimal totalItemPrice;
    //总运费
    private BigDecimal totalDeliveryPrice;
    //应付总价
    private BigDecimal payPrice;
}
//购物车中的商品
@Data
public class Item {
    //商品ID
    private long id;
    //商品数量
    private int quantity;
    //商品单价
    private BigDecimal price;
    //商品优惠
    private BigDecimal couponPrice;
    //商品运费
    private BigDecimal deliveryPrice;
}
//普通用户购物车处理
public class NormalUserCart {
    public Cart process(long userId, Map<Long, Integer> items) {
        Cart cart = new Cart();//把Map的购物车转换为Item列表
        List<Item> itemList = new ArrayList<>();
        items.entrySet().stream().forEach(entry -> {
            Item item = new Item();
            item.setId(entry.getKey());
            item.setPrice(Db.getItemPrice(entry.getKey()));
            item.setQuantity(entry.getValue());
            itemList.add(item);
        });
        cart.setItems(itemList);//处理运费和商品优惠
        itemList.stream().forEach(item -> {
            //运费为商品总价的10%
            item.setDeliveryPrice(item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())).multiply(new BigDecimal("0.1")));
            //无优惠
            item.setCouponPrice(BigDecimal.ZERO);
        });//计算商品总价
        cart.setTotalItemPrice(cart.getItems().stream().map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))).reduce(BigDecimal.ZERO, BigDecimal::add));
        //计算运费总价
        cart.setTotalDeliveryPrice(cart.getItems().stream().map(Item::getDeliveryPrice).reduce(BigDecimal.ZERO, BigDecimal::add));
        //计算总优惠
        cart.setTotalDiscount(cart.getItems().stream().map(Item::getCouponPrice).reduce(BigDecimal.ZERO, BigDecimal::add));
        //应付总价=商品总价+运费总价-总优惠
        cart.setPayPrice(cart.getTotalItemPrice().add(cart.getTotalDeliveryPrice()).subtract(cart.getTotalDiscount()));
        return cart;
    }
}
复制代码

然后实现针对 VIP 用户的购物车逻辑。与普通用户购物车逻辑的不同在于,VIP 用户能享受同类商品多买的折扣。所以,这部分代码只需要额外处理多买折扣部分:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class VipUserCart {
​
​
    public Cart process(long userId, Map<Long, Integer> items) {
        ...
​
​
        itemList.stream().forEach(item -> {
            //运费为商品总价的10%
            item.setDeliveryPrice(item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())).multiply(new BigDecimal("0.1")));
            //购买两件以上相同商品,第三件开始享受一定折扣
            if (item.getQuantity() > 2) {
                item.setCouponPrice(item.getPrice()
                        .multiply(BigDecimal.valueOf(100 - Db.getUserCouponPercent(userId)).divide(new BigDecimal("100")))
                       .multiply(BigDecimal.valueOf(item.getQuantity() - 2)));
            } else {
                item.setCouponPrice(BigDecimal.ZERO);
            }
        });
​
​
        ...
        return cart;
    }
}
复制代码

最后是免运费、无折扣的内部用户,同样只是处理商品折扣和运费时的逻辑差异:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class InternalUserCart {
​
​
    public Cart process(long userId, Map<Long, Integer> items) {
        ...
​
        itemList.stream().forEach(item -> {
            //免运费
            item.setDeliveryPrice(BigDecimal.ZERO);
            //无优惠
            item.setCouponPrice(BigDecimal.ZERO);
        });...
        return cart;
    }
}
复制代码

对比一下代码量可以发现,三种购物车 70% 的代码是重复的。原因很简单,虽然不同类型用户计算运费和优惠的方式不同,但整个购物车的初始化、统计总价、总运费、总优惠和支付价格的逻辑都是一样的。

正如我们开始时提到的,代码重复本身不可怕,可怕的是漏改或改错。比如,写 VIP 用户购物车的同学发现商品总价计算有 Bug,不应该是把所有 Item 的 price 加在一起,而是应该把所有 Item 的 price*quantity 加在一起。

这时,他可能会只修改 VIP 用户购物车的代码,而忽略了普通用户、内部用户的购物车中,重复的逻辑实现也有相同的 Bug。

有了三个购物车后,我们就需要根据不同的用户类型使用不同的购物车了。如下代码所示,使用三个 if 实现不同类型用户调用不同购物车的 process 方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@GetMapping("wrong")
public Cart wrong(@RequestParam("userId") int userId) {
    //根据用户ID获得用户类型
    String userCategory = Db.getUserCategory(userId);
    //普通用户处理逻辑
    if (userCategory.equals("Normal")) {
        NormalUserCart normalUserCart = new NormalUserCart();
        return normalUserCart.process(userId, items);
    }
    //VIP用户处理逻辑
    if (userCategory.equals("Vip")) {
        VipUserCart vipUserCart = new VipUserCart();
        return vipUserCart.process(userId, items);
    }
    //内部用户处理逻辑
    if (userCategory.equals("Internal")) {
        InternalUserCart internalUserCart = new InternalUserCart();
        return internalUserCart.process(userId, items);
    }return null;
}
复制代码

电商的营销玩法是多样的,以后势必还会有更多用户类型,需要更多的购物车。我们就只能不断增加更多的购物车类,一遍一遍地写重复的购物车逻辑、写更多的 if 逻辑吗?

当然不是,相同的代码应该只在一处出现!

如果我们熟记抽象类和抽象方法的定义的话,这时或许就会想到,是否可以把重复的逻辑定义在抽象类中,三个购物车只要分别实现不同的那份逻辑呢?

其实,这个模式就是模板方法模式。我们在父类中实现了购物车处理的流程模板,然后把需要特殊处理的地方留空白也就是留抽象方法定义,让子类去实现其中的逻辑。由于父类的逻辑不完整无法单独工作,因此需要定义为抽象类。

如下代码所示,AbstractCart 抽象类实现了购物车通用的逻辑,额外定义了两个抽象方法让子类去实现。其中,processCouponPrice 方法用于计算商品折扣,processDeliveryPrice 方法用于计算运费。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public abstract class AbstractCart {
    //处理购物车的大量重复逻辑在父类实现
    public Cart process(long userId, Map<Long, Integer> items) {
​
        Cart cart = new Cart();
​
        List<Item> itemList = new ArrayList<>();
        items.entrySet().stream().forEach(entry -> {
            Item item = new Item();
            item.setId(entry.getKey());
            item.setPrice(Db.getItemPrice(entry.getKey()));
            item.setQuantity(entry.getValue());
            itemList.add(item);
        });
        cart.setItems(itemList);
        //让子类处理每一个商品的优惠
        itemList.stream().forEach(item -> {
            processCouponPrice(userId, item);
            processDeliveryPrice(userId, item);
        });
        //计算商品总价
        cart.setTotalItemPrice(cart.getItems().stream().map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))).reduce(BigDecimal.ZERO, BigDecimal::add));
        //计算总运费
        cart.setTotalDeliveryPrice(cart.getItems().stream().map(Item::getDeliveryPrice).reduce(BigDecimal.ZERO, BigDecimal::add));
        //计算总折扣
        cart.setTotalDiscount(cart.getItems().stream().map(Item::getCouponPrice).reduce(BigDecimal.ZERO, BigDecimal::add));
        //计算应付价格
        cart.setPayPrice(cart.getTotalItemPrice().add(cart.getTotalDeliveryPrice()).subtract(cart.getTotalDiscount()));
        return cart;
    }//处理商品优惠的逻辑留给子类实现
    protected abstract void processCouponPrice(long userId, Item item);
    //处理配送费的逻辑留给子类实现
    protected abstract void processDeliveryPrice(long userId, Item item);
}
复制代码

有了这个抽象类,三个子类的实现就非常简单了。普通用户的购物车 NormalUserCart,实现的是 0 优惠和 10% 运费的逻辑:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Service(value = "NormalUserCart")
public class NormalUserCart extends AbstractCart {
​
    @Override
    protected void processCouponPrice(long userId, Item item) {
        item.setCouponPrice(BigDecimal.ZERO);
    }
​
    @Override
    protected void processDeliveryPrice(long userId, Item item) {
        item.setDeliveryPrice(item.getPrice()
                .multiply(BigDecimal.valueOf(item.getQuantity()))
                .multiply(new BigDecimal("0.1")));
    }
}
复制代码

VIP 用户的购物车 VipUserCart,直接继承了 NormalUserCart,只需要修改多买优惠策略:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Service(value = "VipUserCart")
public class VipUserCart extends NormalUserCart {
​
    @Override
    protected void processCouponPrice(long userId, Item item) {
        if (item.getQuantity() > 2) {
            item.setCouponPrice(item.getPrice()
                    .multiply(BigDecimal.valueOf(100 - Db.getUserCouponPercent(userId)).divide(new BigDecimal("100")))
                    .multiply(BigDecimal.valueOf(item.getQuantity() - 2)));
        } else {
            item.setCouponPrice(BigDecimal.ZERO);
        }
    }
}
复制代码

内部用户购物车 InternalUserCart 是最简单的,直接设置 0 运费和 0 折扣即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Service(value = "InternalUserCart")
public class InternalUserCart extends AbstractCart {
    @Override
    protected void processCouponPrice(long userId, Item item) {
        item.setCouponPrice(BigDecimal.ZERO);
    }
​
    @Override
    protected void processDeliveryPrice(long userId, Item item) {
        item.setDeliveryPrice(BigDecimal.ZERO);
    }
}

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
干掉 “重复代码”,这三种方式绝了!
软件工程师和码农最大的区别就是平时写代码时习惯问题,码农很喜欢写重复代码而软件工程师会利用各种技巧去干掉重复的冗余代码。
Java小咖秀
2023/01/30
1.1K0
干掉 “重复代码”,这三种方式绝了!
架构师教你kill祖传石山代码重复/大量ifelse
很多 crud 工程师抱怨业务开发没有技术含量,什么设计模式、高并发都用不到,就是堆CRUD。每次面试被问到“讲讲常用设计模式?”,都只能把单例讲到精通,其他设计模式即使听过也只会简单说说,因为根本没实际用过。 对于反射、注解,也只是知道在框架中用的很多,但自己又不写框架,更不知道该如何使用。
JavaEdge
2021/02/23
7580
架构师教你kill祖传石山代码重复/大量ifelse
架构师教你kill祖传石山代码重复/大量ifelse
很多 crud 工程师抱怨业务开发没有技术含量,什么设计模式、高并发都用不到,就是堆CRUD。每次面试被问到“讲讲常用设计模式?”,都只能把单例讲到精通,其他设计模式即使听过也只会简单说说,因为根本没实际用过。
JavaEdge
2020/10/25
1.1K0
深入解析Spring EL表达式:概念、特性与应用
EL表达式,全称Expression Language,是一种表达式语言,它借鉴了JavaScript和XPath的表达式语言,并设计用来简化在Java Web应用程序中的表达式。在JSP 2.0及以后的版本中,EL表达式被引入,允许开发者在JSP页面上更方便地访问和操作数据。
公众号:码到三十五
2024/03/19
3.6K0
【第十八篇】商城系统-订单中心设计解决方案
  我们需要把相关的静态资源拷贝到nginx,然后动态模板文件拷贝到order项目的templates目录下,然后调整资源的路径。在网关中设置对应的路由即可。
用户4919348
2022/10/04
7230
【第十八篇】商城系统-订单中心设计解决方案
优雅!Spring Boot 3.3 实现职责链模式,轻松应对电商订单流程
在电商系统中,订单的处理流程通常涉及多个步骤,每个步骤都可能有不同的业务逻辑。例如,当用户提交订单时,系统需要校验库存、验证优惠券、计算运费、处理支付、分配物流等。这些操作看似独立,但实际上具有一定的顺序依赖性。为了更好地管理这些业务逻辑,我们需要将这些流程模块化,并按需执行。
程序员皮皮林
2024/10/16
3260
优雅!Spring Boot 3.3 实现职责链模式,轻松应对电商订单流程
JavaSE综合练习之大数据手机商城
需要用到的Jar包 链接:https://pan.baidu.com/s/1Y4CT_ePPKc2lDdh_ylMCew 提取码:didj
Maynor
2021/12/07
6330
JavaSE综合练习之大数据手机商城
谷粒商城-高级篇(购物车)
​ 参考京东,在点击购物车时,会为临时用户生成一个name为user-key的cookie临时标识,过期时间为一个月,如果手动清除user-key,那么临时购物车的购物项也被清除,所以 user-key 是用来标识和存储临时购物车数据的
OY
2022/03/20
7020
谷粒商城-高级篇(购物车)
BS1049-基于SpringSSM开发实现互联网电商平台及后台系统
基于SpringSSM的电商平台及后台系统,系统采用多层MVC软件架构,采用Java SpringMVC Mybatis Mysql框架实现互联网电商商品数据的爬虫采集,商品销售数据分析以及商品在线销售电商程序。系统爬虫端主要采用JSOUP+HttpClient实时采集分析互联网电商平台网站数据,
计算机程序优异哥
2022/12/05
5380
购物车的原理及实现.(仿京东实现原理)
2018年1月20号更新: 这个博客是自己对着传智的视频一点点学习的, 敲完了一整遍代码感觉自己也学到挺多东西,现在好多小伙伴说链接失效了, 现在补上传智的整套视频和源码,有问题给我留言。 链接:https://pan.baidu.com/s/1c3MMv6o 密码:w9py 2017年7月14日更新:  有很多小伙伴想要项目资料和源码, 我重新整理了一份传了上来:  这次更新的为项目全套视频及所有源码资料: 链接:https://pan.baidu.com/s/1c3MMv6o 密码:w9py 今天来
一枝花算不算浪漫
2018/05/18
1.5K0
移动商城第七篇【购物车增删改查、提交订单】
把商品加入购物车 接下来我们要做的就是将商品加入到购物车中。我们这次使用的是Cookie来将用户的信息存储起来。那为什么要用cookie呢?? 如果将购物车存储在Session,那么Session里边的值容易丢失。 存储在Cookie中,用户不需要登陆就能够查看得到购物车的数据。 那我们现在决定将购物车存储在Cookie中了,那Cookie中保存的是怎么样的字符串数据呢??? 显而易见我们第一时间想到的就是JSON JSON能够很好地将字符串转成Java对象,将Java对象转成JSON给浏览器进行显示! 看
Java3y
2018/04/02
4K0
移动商城第七篇【购物车增删改查、提交订单】
订单的处理原理及代码实现.
上一篇博文我们已经介绍了购物车的原理及实现, 那么购物车再往后就是提交订单了, 订单的实现方式是怎么样的呢? 那么下面就让我们来一起学习下. 提交订单有几个关键点:  1, 用户必须登录 2, 购物
一枝花算不算浪漫
2018/05/18
2.4K0
购物车的原理以及实现
  今天模拟京东的购物车实现原理完成了购物车模块的开发, 给大家分享下。 京东的购物车实现原理:在用户登录和不登录的状态下对购物车存入cookie还是持久化到redis中的实现。下面就来具体说次购物车的实现过程 两种情况: 用户登录,购物车存入redis中 用户未登录,购物车存入cookie中 比较两种方式的优缺点:  cookie:优点:数据保存在用户浏览器中,不占用服务端内存;用户体检效果好;代码实现简单      缺点:cookie的存储空间只有4k;更换设备时,购物车信息不能同步;cookie禁用,
用户2146856
2018/05/18
2.4K0
【第十七篇】商城系统-购物车功能设计
  Nginx接收了 cart.msb.com这个域名的访问,那么会把服务反向代理给网关服务,这时网关服务就需要把该请求路由到购物车服务中。我们需要修改网关服务的配置
用户4919348
2022/10/04
8120
【第十七篇】商城系统-购物车功能设计
谷粒商城-高级篇(消息队列)
签收+拒收【并返回服务器入队】multiple:批量签收,requeue:是否重新入队
OY
2022/04/11
2K0
谷粒商城-高级篇(消息队列)
Cookie实现购物车功能
---- 这里的购物车暂时存放书,后期把参数改成Object,把方法抽取成接口,只要实现了接口的Object类都可以放进购物项,这样就实现了购物任何物品 使用购物项因为一个购物项可以包含某种商品的数量,总价等,反之则需要把商品重复存放到购物车,没有用户体验 购物车用HashMap,键存放书id,值存放购物项 1. 设计bean 书 public class Book implements Serializable{ //因为对象传输需要实现序列化接口 //后面代码中id作为Map的
晚上没宵夜
2020/03/10
1.8K0
双十一折扣计算技术详解:电商系统中的最优优惠组合与性能优化
在现代电子商务平台中,复杂的优惠政策和多样的折扣类型(如满减、打折、折上折等)为用户提供了丰富的选择,但也增加了用户选择的复杂度。本文将探讨如何通过前端算法实现购物系统中的优惠折扣计算,帮助用户在结算时自动选择最优折扣组合,以提升用户体验和购物满意度。
一键难忘
2024/11/02
3492
Spring高级技术应用——百战商城实现(下)
需要用到pojo,但是我们可以通过依赖Mapper项目来简介添加Pojo项目 需要用到Spring Data整合Solr的坐标
时间静止不是简史
2020/07/27
1.2K0
Spring高级技术应用——百战商城实现(下)
加入购物车怎么操作_买家加入购物车不买怎么办
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/166633.html原文链接:https://javaforall.cn
全栈程序员站长
2022/09/19
7230
加入购物车怎么操作_买家加入购物车不买怎么办
【愚公系列】2022年11月 微信小程序-优购电商项目-商品购物车页面
网上购物车是顾客在进行网上购物时所必须使用的购物工具。它用来临时存储用户选择的商品,协助顾客从虚拟商场中选取商品、携带商品,并到虚拟的收银台结账。
愚公搬代码
2022/11/14
4500
【愚公系列】2022年11月 微信小程序-优购电商项目-商品购物车页面
推荐阅读
相关推荐
干掉 “重复代码”,这三种方式绝了!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验