一段代码被老大要求重构了六次,我心态崩了

2023-10-28 23:40

本文主要是介绍一段代码被老大要求重构了六次,我心态崩了,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  • 前言

  • 第一次 按类型筛选瓜类

  • 第二次 按重量筛选瓜类

  • 第三次 按类型和重量筛选瓜类

  • 第四次 将行为作为参数传递

  • 第五次  一次性加了100个过滤条件

  • 第六次  引入泛型

  • 简而言之Lambda

  • 总结

前言

进来给大家八卦一段,看看我自己都去干啥了?话说最近公司接了一个农产品交易网站新项目,因为一段代码重构问题差点和老大干起来,本来以为是老大故意刁难我。最后还是发现是我太菜了????,事情是这个样子滴!

在周例会上,老大告知我们最近接了一个农产品交易平台,主要用于全省农产品线上交易。首当其中,就是要把我们甘肃省的黄河蜜推销出去,我就被安排卖瓜!嗷,不,卖瓜这个功能我负责开发????;很快我设计下面的类来定义瓜   Melon 类:

/*** 瓜* @author Milo Lee* @date 2021-04-07 13:21*/
public class Melon {/**品种*/private final String type;/**重量*/private final int weight;/**产地*/private final String origin;public Melon(String type, int weight, String origin) {this.type = type;this.weight = weight;this.origin = origin;}// getters, toString()方法省略
}

经过一顿CRUD骚操作,写完了瓜类增删改查工作,交工下班????。

第一次 按类型筛选瓜类

第二天,老大给我提了一个问题,说增加能够按瓜类型对瓜进行过滤。这不很简单吗?于是,于是我创建了一个  Filters 类,   实现了一个filterMelonByType方法

/*** @author Milo Lee* @date 2021-04-07 13:25*/
public class Filters {/*** 根据类型筛选瓜类* @param melons 瓜类* @param type 类型* @return*/public static List<Melon> filterMelonByType(List<Melon> melons, String type) {List<Melon> result = new ArrayList<>();for (Melon melon: melons) {if (melon != null && type.equalsIgnoreCase(melon.getType())) {result.add(melon);}}return result;}
}

搞定,我们来测试一下

    public static void main(String[] args) {ArrayList<Melon> melons = new ArrayList<>();melons.add(new Melon("羊角蜜", 1, "泰国"));melons.add(new Melon("西瓜", 2, "三亚"));melons.add(new Melon("黄河蜜", 3, "兰州"));List<Melon> melonType = Filters.filterMelonByType(melons, "黄河蜜");melonType.forEach(melon->{System.out.println("瓜类型:"+melon.getType());});}

没毛病,拿给老大看看去,老大看了我的代码说:如果我让你在增加一个按重量筛选瓜类,你打算怎么写?回去考虑一下吧,这家伙不会故意找我茬把?????

第二次 按重量筛选瓜类

回到座位的我心想,上次我已经实现了按类型筛选瓜类,那我给他copy一份改改吧!

如下所示:

    /*** 按照重量过滤瓜类* @param melons* @param weight* @return*/public static List<Melon> filterMelonByWeight(List<Melon> melons, int weight) {List<Melon> result = new ArrayList<>();for (Melon melon: melons) {if (melon != null && melon.getWeight() == weight) {result.add(melon);}}return result;}
public static void main(String[] args) {ArrayList<Melon> melons = new ArrayList<>();melons.add(new Melon("羊角蜜", 1, "泰国"));melons.add(new Melon("西瓜", 2, "三亚"));melons.add(new Melon("黄河蜜", 3, "兰州"));List<Melon> melonType = Filters.filterMelonByType(melons, "黄河蜜");melonType.forEach(melon->{System.out.println("瓜类型:"+melon.getType());});List<Melon> melonWeight = Filters.filterMelonByWeight( melons,3);melonWeight.forEach(melon->{System.out.println("瓜重量:"+melon.getWeight());});}

程序员最喜欢的方式,CV搞定,哈哈。但是我发现filterByWeight()与   filterByType() 非常相似,就是过滤条件不同。我心想,老大不会让我写一个按类型和重量筛选瓜类吧。拿着我的代码去给老大看,果不其然,怕什么来什么????。

第三次 按类型和重量筛选瓜类

为了满足完成老大的任务,我将上面的代码进行了糅合,很快写了如下的代码

    /*** 按照类型和重量来筛选瓜类* @param melons* @param type* @param weight* @return*/public static List<Melon> filterMelonByTypeAndWeight(List<Melon> melons, String type, int weight) {List<Melon> result = new ArrayList<>();for (Melon melon: melons) {if (melon != null && type.equalsIgnoreCase(melon.getType()) && melon.getWeight() == weight) {result.add(melon);}}return result;}

老大看了我的代码说,看来你还是没有理解我的意思。假如今天不光我,还有客户继续这样提需求。

那么   Filters 将会有很多这样类似的方法,也就是说写了很多样板代码(代码冗余但又不得不写);

在我们程序员看来,这是不能接受的。如果继续添加新的过滤条件,则代码将变得难以维护且容易出错。你去了解一下lambda表达式函数式接口知识点,再修改一下你的代码。我已经确定了,他就是和我过不去????

第四次 将行为作为参数传递

经过上面的三番折腾。我发现理论上Melon类的任何属性都有可能作为过滤条件,这样的话我们的Filter类将会有大量的样板代码,而且有些方法会非常复杂。

其实我们可以发现,我们每写一个方法,都对应一种查询行为,查询行为必然对应一种过滤条件。有没有办法我们写一个方法,将查询行为作为参数传递进去,从而返回我们的结果呢?

那么给它取了一个名字:行为参数化,在下图中进行了说明(左侧显示了我们现在拥有的;右侧显示了我们想要的),有没有发现样板代码会明显减少????

如果我们将过滤条件视为一种行为,那么将每种行为视为接口的实现是非常直观的。经过分析我们发现以上所有这些行为都有一个共同点:过滤条件boolean 类型的返回   。抽象一个接口如下

public interface MelonPredicate {boolean test(Melon melon);
}

例如,过滤 黄河蜜可以这样写: HHMMelonPredicate

public class HHMMelonPredicate implements MelonPredicate {@Overridepublic boolean test(Melon melon) {return "黄河蜜".equalsIgnoreCase(melon.getType());}}

以此类推,我们也可以过滤一定重量的瓜:

public class WeightMelonPredicate implements MelonPredicate {@Overridepublic boolean test(Melon melon) {return melon.getWeight() > 5000;}}

其实熟悉设计模式的同学应该知道这就是:策略设计模式

主要思想就是让系统在运行时动态选择需要调用的方法。所以我们可以认为   MelonPredicate 接口统一了所有专用于筛选瓜类的算法,并且每种实现都是一种策略,我们也可以把它理解为一种行为。

目前,我们利用策略设计模式,将查询行为进行了抽象。我们还需要一个方法接收   MelonPredicate 参数。于是我定义了  filterMelons() 方法,如下所示:

public static List<Melon> filterMelons(List<Melon> melons, MelonPredicate predicate) {List<Melon> result = new ArrayList<>();for (Melon melon: melons) {if (melon != null && predicate.test(melon)) {result.add(melon);}}  return result;
}

大功告成,测试一下,果然比之前好用多了,再让老大瞅瞅去

List<Melon> hhm = Filters.filterMelons(melons, new HHMMelonPredicate());List<Melon> weight = Filters.filterMelons(melons, new WeightMelonPredicate());

第五次  一次性加了100个过滤条件

就在我沾沾自喜时候,老大又给他泼了一盆冷水。他说你以为我们的平台就买黄河蜜啊,如果前前后后有几十种瓜品种,我给你列出100种过滤条件,你怎么办?

我的心里一万个草泥马在奔腾啊????!老大是不是存心和我过不去啊!虽然经过上次改造,我的代码已经足够灵活,但是如果突然增加100个过滤条件,我仍然需要编写100个策略类来实现 每一个过滤条件。然后我们需要将策略传递给   filterMelons() 方法。

有没有不需要创建这些类的办法那?聪明的我很快发现可以使用java匿名内部类

如下所示:

List<Melon> europeans = Filters.filterMelons(melons, new MelonPredicate() {@Overridepublic boolean test(Melon melon) {return "europe".equalsIgnoreCase(melon.getOrigin());}});

虽然向前跨了一大步,但好像无济于事。我还是需要编写大量的代码实现此次需求。设计匿名内部类的目的,就是为了方便 Java 程序员将代码作为数据传递。有时候,匿名内部类看这比较复杂,这时候,我突然想起来老大让我学的lambda表达式,我可以用它来简化

List<Melon> europeansLambda = Filters.filterMelons(melons, m -> "europe".equalsIgnoreCase(m.getOrigin())
);

果然看这帅多了!!!,就这样,我又一次成功完成了任务。我兴冲冲的拿着代码让老大去看看。

第六次  引入泛型

老大看着我的代码说,嗯,不错!脑袋终于开窍了。现在你考虑一下,我们的平台是做农产品的,也就是肯定不止瓜这一类水果,如果换做其他的水果,你的代码如何修改?

目前我们的MelonPredicate仅支持   Melon 类。这家伙怎么搞?说不定哪天他要买蔬菜、海参可怎么搞,总不能给他再创建好多类似MelonPredicate的接口吧。这个时候突然想起老师讲过的泛型,该它发挥作用了!

于是我定义了一个新接口Predicate

@FunctionalInterface
public interface Predicate<T> {boolean test(T t);}

接下来,我们重写该   filterMelons() 方法并将其重命名为   filter()

public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {List<T> result = new ArrayList<>();for (T t: list) {if (t != null && predicate.test(t)) {result.add(t);}}  return result;}

现在,我们可以这样过滤瓜类  :

List<Melon> watermelons = Filters.filter(melons, (Melon m) -> "Watermelon".equalsIgnoreCase(m.getType()));

同样的,我们也可以对数字做同样的事情:

List<Integer> numbers = Arrays.asList(1, 13, 15, 2, 67);List<Integer> smallThan10 = Filters.filter(numbers, (Integer i) -> i < 10);

回过头来复盘一下,我们发现自从使用Java 8函数式接口和lambda表达式后,代码发生了明显的变化。

不知道细心的伙伴有没有发现我们上面的  Predicate 接口上面多了一个@FunctionalInterface 上的注解,它就是标记函数式接口的。

至此,我们通过一个需求的演变过程,了解了lambda和函数式接口的概念,同时也加深对它们的理解。其实熟悉java8的朋友都知道,在我们的 java.util.function 包下包含40多个此类接口

函数式接口lambda表达式组成了一个强大的团队。根据上面的例子,我们知道函数式接口是我们行为的高度抽象,lambda表达式我们可以看出这种行为的具体实现的一个实例。

Predicate<Melon> predicate = (Melon m)-> "Watermelon".equalsIgnoreCase(m.getType());

简而言之Lambda

lambda表达式由三部分组成,如下图所示:

以下是lambda表达式各部分的描述:

  • 在箭头的左侧,是在lambda表达式主体中使用的参数。

  • 在箭头的右侧,是lambda主体 。

  • 箭头只是lambda参数和主体的分隔符。

lambda的匿名类版本如下:

List<Melon> europeans = Filters.filterMelons(melons, new Predicate<Melon>() {@Overridepublic boolean test(Melon melon) {return "Watermelon".equalsIgnoreCase(melon.getType());}});

现在,如果我们查看lambda表达式及其匿名类版本,可以从下面四方面来描述lambda表达式

我们可以将 lambda 表达式定义为一种 简洁、可传递的匿名函数,首先我们需要明确 lambda 表达式本质上是一个函数,虽然它不属于某个特定的类,但具备参数列表、函数主体、返回类型,甚至能够抛出异常;其次它是匿名的,lambda 表达式没有具体的函数名称;lambda 表达式可以像参数一样进行传递,从而简化代码的编写。

Lambda支持行为参数化,在前面的例子中,我们已经证明这一点。最后,请记住,lambda表达式只能在函数式接口的上下文中使用。

总结

在本文中,我们重点介绍了函数式接口的用途和可用性,我们将研究如何将代码从开始的样板代码现演变为基于函数式接口的灵活实现。希望对大家理解函数式接口有所帮助,谢谢大家。

推荐好文

强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

分享一套基于SpringBoot和Vue的企业级中后台开源项目,代码很规范!

能挣钱的,开源 SpringBoot 商城系统,功能超全,超漂亮

这篇关于一段代码被老大要求重构了六次,我心态崩了的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:https://blog.csdn.net/weixin_38405253/article/details/116810610
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/296768

相关文章

Java中调用数据库存储过程的示例代码

《Java中调用数据库存储过程的示例代码》本文介绍Java通过JDBC调用数据库存储过程的方法,涵盖参数类型、执行步骤及数据库差异,需注意异常处理与资源管理,以优化性能并实现复杂业务逻辑,感兴趣的朋友... 目录一、存储过程概述二、Java调用存储过程的基本javascript步骤三、Java调用存储过程示

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性:

MySQL数据库的内嵌函数和联合查询实例代码

《MySQL数据库的内嵌函数和联合查询实例代码》联合查询是一种将多个查询结果组合在一起的方法,通常使用UNION、UNIONALL、INTERSECT和EXCEPT关键字,下面:本文主要介绍MyS... 目录一.数据库的内嵌函数1.1聚合函数COUNT([DISTINCT] expr)SUM([DISTIN

Java实现自定义table宽高的示例代码

《Java实现自定义table宽高的示例代码》在桌面应用、管理系统乃至报表工具中,表格(JTable)作为最常用的数据展示组件,不仅承载对数据的增删改查,还需要配合布局与视觉需求,而JavaSwing... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

Go语言代码格式化的技巧分享

《Go语言代码格式化的技巧分享》在Go语言的开发过程中,代码格式化是一个看似细微却至关重要的环节,良好的代码格式化不仅能提升代码的可读性,还能促进团队协作,减少因代码风格差异引发的问题,Go在代码格式... 目录一、Go 语言代码格式化的重要性二、Go 语言代码格式化工具:gofmt 与 go fmt(一)

HTML5实现的移动端购物车自动结算功能示例代码

《HTML5实现的移动端购物车自动结算功能示例代码》本文介绍HTML5实现移动端购物车自动结算,通过WebStorage、事件监听、DOM操作等技术,确保实时更新与数据同步,优化性能及无障碍性,提升用... 目录1. 移动端购物车自动结算概述2. 数据存储与状态保存机制2.1 浏览器端的数据存储方式2.1.

基于 HTML5 Canvas 实现图片旋转与下载功能(完整代码展示)

《基于HTML5Canvas实现图片旋转与下载功能(完整代码展示)》本文将深入剖析一段基于HTML5Canvas的代码,该代码实现了图片的旋转(90度和180度)以及旋转后图片的下载... 目录一、引言二、html 结构分析三、css 样式分析四、JavaScript 功能实现一、引言在 Web 开发中,

Python如何去除图片干扰代码示例

《Python如何去除图片干扰代码示例》图片降噪是一个广泛应用于图像处理的技术,可以提高图像质量和相关应用的效果,:本文主要介绍Python如何去除图片干扰的相关资料,文中通过代码介绍的非常详细,... 目录一、噪声去除1. 高斯噪声(像素值正态分布扰动)2. 椒盐噪声(随机黑白像素点)3. 复杂噪声(如伪

Java Spring ApplicationEvent 代码示例解析

《JavaSpringApplicationEvent代码示例解析》本文解析了Spring事件机制,涵盖核心概念(发布-订阅/观察者模式)、代码实现(事件定义、发布、监听)及高级应用(异步处理、... 目录一、Spring 事件机制核心概念1. 事件驱动架构模型2. 核心组件二、代码示例解析1. 事件定义

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部