Loading [MathJax]/jax/output/CommonHTML/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 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
python多版本共存最好的解决方案-虚拟环境
在开发Python应用程序的时候,系统安装的Python3只有一个版本:3.4。所有第三方的包都会被pip安装到Python3的site-packages目录下。 如果我们要同时开发多个应用程序,那这些应用程序都会共用一个Python,就是安装在系统的Python 3。如果应用A需要jinja 2.7,而应用B需要jinja 2.6怎么办? 这种情况下,每个应用可能需要各自拥有一套“独立”的Python运行环境。virtualenv就是用来为一个应用创建一套“隔离”的Python运行环境。 具体的pytho
IT架构圈
2018/06/01
2K0
Python虚拟环境virtualenv手册
python 的虚拟环境可以为一个 python 项目提供独立的解释环境、依赖包等资源,既能够很好的隔离不同项目使用不同 python 版本带来的冲突,而且还能方便项目的发布。
仲君Johnny
2024/01/24
2940
[转载]windows下安装Python虚拟环境virtualenv,virtualenvwrapper-win
1 前言 由于Python的版本众多,还有Python2和Python3的争论,因此有些软件包或第三方库就容易出现版本不兼容的问题。 通过 virtualenv 这个工具,就可以构建一系列 虚拟的Python环境 ,然后在每个环境中安装需要的软件包(配合 pip 使用),这一系列的环境是相互隔离的。作为一个独立的环境就不容易出现版本问题,还方便部署。 2 安装 pip install virtualenv 3 virtualenv的基本使用 3.1 创建虚拟环境 virtualenv venv window
程序员同行者
2018/07/02
1.8K0
Python自学成才之路 玩转虚拟环境
Python自带的那个环境是系统环境,同一个项目的不同版本可能会依赖不同版本的依赖包,如果都放在系统环境下会使系统环境变得很庞大,同时操作起来也不太方便,如果给每个项目都单独配置一个环境,各个项目之间互不干扰,开发起来就方便些,每个项目单独的环境叫做虚拟环境。
我是李超人
2020/09/02
5600
virtualenv,非常强大的Python虚拟环境工具,强烈推荐~
在进行Python开发项目时,经常会用到各种依赖库,为了保持每个代码项目的独立性,以及避免与其他项目库相互干扰,导致版本冲突,这时候单独创建一个虚拟环境就很有必要。
派大星的数据屋
2025/04/13
1400
virtualenv,非常强大的Python虚拟环境工具,强烈推荐~
工具篇 | Python虚拟环境的搭建与管理【virtualenv/virtualenv-wrapper】
当我们在使用Python的时候,经常会使用pip来安装第三方包,那么我们会遇到这样两个问题:
LogicPanda
2019/07/30
9600
【Python学习笔记】-虚拟环境virtualenv
在开发python应用程序的时候,系统安装的python3只有一个版本:3.4。所有的第三方的包都回被pip安装到python3的site-packages目录下。
DevOps在路上
2023/05/16
4430
浅谈virtualenv(虚拟环境)
简介  virtualenv为应用提供了隔离的Python运行环境,解决了不同应用间多版本的冲突问题。 例如: 如果我们要同时开发多个应用程序,那这些应用程序都会共用一个Python,就是安装在系统的Python 3。如果应用A需要jinja 2.7,而应用B需要jinja 2.6怎么办? 这种情况下,每个应用可能需要各自拥有一套“独立”的Python运行环境。virtualenv就是用来为一个应用创建一套“隔离”的Python运行环境。 安装 pip3 install virtualenv 基本使用 为一
人生不如戏
2018/07/04
7240
python笔记41-虚拟环境virtualenv
如果你是一个python初学者,我是不建议你搞python虚拟环境的,我看到很多python的初学者同学,使用最新版的pycharm,新建一个工程时候默认就是venu虚拟环境。 然后在使用cmd里面pip安装第三方包的时候,在工程里面死活导入不成功,搞的开始怀疑人生。(你给他讲这是虚拟环境venu,他会一脸懵逼!)
上海-悠悠
2019/09/02
1.2K0
mac python virtualenv 安装虚拟环境
主要是为了解决在同一个系统中存在多个项目的情况, 采用虚拟的方式,为每个项目创建各自的环境,使各个项目不会相互干扰
onety码生
2018/11/21
8730
Win10下Python虚拟环境virtualenv安装和使用
在python开发中,我们可能会遇到一种情况,就是当前的项目依赖的是某一个版本,但是另一个项目依赖的是另一个版本,这样就会造成依赖冲突,而virtualenv就是解决这种情况的,virtualenv通过创建一个虚拟化的python运行环境,将我们所需的依赖安装进去的,不同项目之间相互不干扰,如下所示。
菲宇
2022/12/21
2.9K0
Win10下Python虚拟环境virtualenv安装和使用
使用虚拟环境virtualenv/Virtualenvwrapper隔离多个python
系统中的多个python混用会导致$PYTHONPATH混乱,或者各个工程对于package的版本要求不同等等情况。有一个简单的解决方案就是用virtualenv来隔离多个python,其本质只是实现隔离不同python中$PYTHONPATH的路径,当然也可以衍生到隔离多个$PATH。
全栈程序员站长
2022/07/09
4070
python 环境安装
Python官网:http://www.python.org/ Python文档地址:http://www.python.org/doc/ Python模块仓库 https://pypi.python.org/pypi
py3study
2020/01/15
7130
python 环境安装
python安装虚拟环境步骤_python虚拟环境迁移
http://blog.csdn.net/pipisorry/article/details/47008981
全栈程序员站长
2022/09/27
8K0
python安装虚拟环境步骤_python虚拟环境迁移
使用virtualenv搭建Python虚拟环境
virtualenv用来部署独立Python的一个工具,用来解决版本依赖,及不兼容的项目。
苦叶子
2018/07/25
1K0
使用virtualenv搭建Python虚拟环境
干货 | 5分钟教你科学使用Python虚拟环境
虚拟环境可以看作是原生Python的副本,但是标准库都是一样的,每次都复制是不合算的
网络安全自修室
2022/12/06
7320
干货 | 5分钟教你科学使用Python虚拟环境
windows下面使用多版本Python安装指定版本的虚拟环境
今天博主在搭建一个项目的时候,希望使用最新版的Python3.7版本,但是Python3.6的版本也要留下,那么问题来了,如何解决这个问题呢?如何在windows下面使用多版本Python安装指定版本的虚拟环境呢?这篇文章我就来解决这个问题。
啃饼思录
2018/10/15
1.9K0
python的虚拟环境
-p PYTHON_EXE, --python=PYTHON_EXE 指定生成的虚拟环境使用的Python解释器:
py3study
2020/01/02
6290
Python虚拟环境(pipenv、venv、conda一网打尽)[通俗易懂]
要搞清楚什么是虚拟环境,首先要清楚Python的环境指的是什么。当我们在执行python test.py时,思考如下问题:
全栈程序员站长
2022/09/27
44.9K0
Python虚拟环境(pipenv、venv、conda一网打尽)[通俗易懂]
安装python虚拟环境
1 virtualenv pip install virtualenv 普通安装 virtualenv python3env python3env是自己定的名字 指定安装的python版本 virtu
98k
2018/04/11
9180
安装python虚拟环境
推荐阅读
相关推荐
python多版本共存最好的解决方案-虚拟环境
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验